Notice: Function wp_enqueue_script was called incorrectly. Scripts and styles should not be registered or enqueued until the wp_enqueue_scripts, admin_enqueue_scripts, or login_enqueue_scripts hooks. This notice was triggered by the jquery handle. Please see Debugging in WordPress for more information. (This message was added in version 3.3.0.) in /mnt/webroot/wandoer/code.wandoer/wp-includes/functions.php on line 6260
多线程 – 豌豆

标签: 多线程

  • 从单例模式谈起(二):volatile 关键字

    在前一部分(从单例模式谈起(一))我们讨论单例模式时,谈到了 Double-Checked Locking 问题。我们讲, volatile 关键字并不能解决Double-Checked Locking 问题。这一节我们讨论与此相关的问题。
    现代编译器为了提高程序的效率而对代码做了很多优化。这些优化大部分是有益的,但是也为多线程编程带来问题。

    寄存器缓存

    我们来看如下代码:

    x = 0;
    Thread1 {
        lock();
        x++;
        unlock();
    }
    Thread2 {
        lock();
        x++;
        unlock();
    }

    x++ 的值有锁保护,所以它是独占的,x++ 的行为不会被并发破坏。那么 x 的值必然为 2。 然而现实中并非一定如此:编译器有可能为了提高 x 的访问速度而将 x 放入线程的某个寄存器中,而线程的寄存器是线程私有的。 如果 Thread1 先获得了锁,那么程序的执行过程有可能是这样的:

    1. [Thread1] 读取 x 到寄存器 R1. (R1 = x = 0)
    2. [Thread1] R1++. unlock. 由于之后可能还会访问 x, 所以 Thread1 不将 R1 写回 x . (x = 0)
    3. [Thread2] 读取 x 到寄存器 R2 (R2 = x = 0)
    4. [Thread2] R2++
    5. [Thread2] 将 R2 写回 x (x = R2 =1)
    6. [Thread1] 在某个时候将 R1 写回 x (x = R1 = 1)

    由此可见,即使正确的加了锁,也不能保证多线程安全。

    (更多…)

  • 从单例模式谈起(一)

    单例模式可能是大家最为熟知的一种设计模式,本身没什么好谈的。但是在 C++ 中,由单例模式可以引出一系列的问题,可能会比较有意思,这里探讨一下。

    常见的简单实现

    1.使用 static 实现

    class S1 {
        public:
            static S1& GetInstance() {
                static S1 s;
                return s;
            }   
            void fun() {}
    
        private:
            S1(){cout<<"S1 inited"<<endl;}
            S1(const S1&) = delete;
            S1& operator=(const S1&) = delete;
    };
    
    class S2{ 
        public:
            static S2& GetInstance(){
                return s;
            }   
            void fun(){}
        private:
            S2(){cout<<"S2 inited"<<endl;}
            //...
        private:
            static S2 s;
    };
    S2 S2::s;

    其中 S2 是要避免的。因为 1. 类的静态成员变量的初始化时间一般早于 main 函数 2. 如果静态成员的初始化里调用了其它类,可能出现未定义的错误。

    2.使用指针判断是否初始化

    class S3{
        public:
            static S3& GetInstance(){
                if(s == 0)
                    s = new S3();
                     return *s;
            }
    
        private:
            S3(){}
            static S3 *s;
    };

    这两种方式在单线程程序中使用都是可以的,但如果在多线程中,就会出现问题。

    Magic Static

    对于 S1 要注意, C++ 局部静态变量的初始化可能不是线程安全的 , 这就是 Magic Static ,是指 返回一个静态局部变量的引用 的用法,在某些情况下,如S1 可能会被编译器这样解析:

    (更多…)

  • 多线程编程中的一些原则

    关于 C++ 多线程编程一的些基本知识可以参考本博客的《C++11/14 新特性(多线程)》 ,《Unix线程基础》。本章不是多线程编程教程,而是个人经验的一些总结。这些经验有一些可能是不正确的,希望在今后的编程中实践、改进。

    线程同步的四项基本原则:

    1. 最低限度地共享对象。对象尽量不要暴露给别的线程,如果需要暴露,优先考虑 immutable对象。否则尽量使用同步措施来充分地保护它
    2. 尽量使用高级地并发编程构件,如 任务队列、生产者消费者模式等
    3. 只用非递归的互斥器和条件变量,慎用读写锁,尽量少用信号量
    4. 除了使用 atomic 整数外,不要自己编写 lock-free 代码,也不要用”内核级”同步原语

    互斥器 Mutex

    mutex 是最常用的同步原语,它保护一个临界区,任何时候最多只能有一个线程能够访问 mutex 保护的域。使用 mutex 主要是为了保护共享数据。一般原则有:

    • 使用 RAII手法封装 mutex 的创建、销毁、加锁、解锁操作,充分保证锁的有效期等于其作用域,而不会因为中途返回或异常而忘记解锁。这类似于 Java 的synchronized 或 C# 的 using 语句。
    • 使用非递归的 mutex
    • 尽量不要人为地调用 lock() 和 unlock()函数,将这些操作交给栈上的 guard 对象,利用其构造与析构函数。
    • 不要跨线程地加解锁,避免在不同的函数中分别加锁\解锁,避免在不同的语句分支中加锁\解锁
    • 每当构造 guard 对象时,需要考虑栈上已有的锁,防止因加锁顺序不同而导致死锁
    • 避免跨进程的 mutex, 进程间通讯尽量使用 TCP sockets

    只使用非递归地 mutex

    (更多…)

  • C++11/14 新特性 (多线程)

    在 C++11 之前 ,C++ 标准并没有提供统一的并发编程标准,也没有提供语言级别的支持。这导致我们在编写可移植的多线程程序时很不方便,往往需要面向不同的平台进行不同的实现,或者引入一些第三方平台,如Boost,pthread_win32 等。 从C++11开始 ,对并发编程进行了语言级别的支持,使用使用C++进行并发编程方便了很多。这里介绍C++11并发编程的相关特性。

    1 线程

    1.1 线程的创建

    std::thread 的构造函数如下:

    template< class Function, class... Args > 
    explicit thread( Function&& f, Args&&... args );

    我们只需要提供线程函数或函数对象,即可以创建线程,并可以同时指定线程函数的参数。

    #include <iostream>
    #include <thread>
    using namespace std;
    
    void func(const string& str){
        cout<<str<<endl;
    }
    
    class FuncCls{
    public:
        void operator()(){
            cout<<"Funcls called"<<endl;
        }
    };
    
    int main(void){
        thread t1(func,"Hello World");
        FuncCls fcls;
        thread t2(fcls);
    
        if(t1.joinable()){
            t1.join();
        }
        t2.join();
        return 0;
    }

    join函数将会阻塞线程,直到线程函数执行完毕,主线程才会接着执行。如果线程函数有返回值,返回值将被忽略。 在使用线程对象的过程中,我们需要注意线程对象的生命周期。如果线程对象先于线程函数结束,那么将会出现不可预料的错误。可以通过线程阻塞的方式来等待线程函数执行完(join),或让线程在后台执行。 如果不希望线程被阻塞,可以调用线程的 detach() 函数,将线程与线程对象分离。但需要注意的是,detach 之后的线程无法再使用join来进行阻塞了,即detach之后的线程,我们无法控制了。当我们不确定一个线程是否可以join时,可以先使用 thead::joinable() 来进行判断。

    另外,我们还可以通过 std::bind, lambda 来创建线程(其实就是使用函数对象创建线程)。

    线程不可以被复制,但是可以被移动:
    (更多…)

  • Unix线程基础

    1 线程标识

    每个线程都有一个唯一标识:线程ID. 线程ID用 pthread_t 数据类型来表示,该结构体在不同的系统上有不同的实现,某些操作系统将其实现为 unsigned long ,某些操作系统将其实现为结构体。所有可移植代码中不可以对其直接比较,必须使用一个函数来对两个线程ID进行比较。

    //返回值:若相等,则为非0;否则,返回0
    int pthread_equl(pthread_t t1, pthread_t t2);
    

    不同的实现方式带来的影响就是,不能使用一种可移植的方式来打印该数据类型的值。
    获取当前线程:

    //Unix
    pthread_t pthread_self(void);
    
    //Windows
    HANDLE GetCurrentThread(void);
    

    2 线程创建

    新增的线程可以通过调用pthread_create 函数来创建

    #include <pthread.h>
    //thread: 当成功时,指向新增线程的内存单元
    //返回值:若成功,则返回0;否则 返回错误编号
    int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                              void *(*start_routine) (void *), void *arg);
    

    新创建的线程从 start_routine 函数的地址开始运行。该函数有一个 void* 类型的指针做为参数。如果函数需要多个参数,需要将参数做为结构体,然后将结构体地址做为参数传入。 创建线程时并不能保证新增线程优先于其它线程先运行,这依赖于操作系统的线程实现与调度算法。

    (更多…)