精华内容
下载资源
问答
  • 2022-02-21 19:07:05

    线程间的同步方法大体可以分为两类:用户模式和内核模式。内核模式就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态,而用户模式就是不需要切换到内核态,只在用户态完成操作。

    • 用户模式下的方法有:原子操作(例如一个单一的全局变量)、临界区。特点是:同步速度特别快。
    • 内核模式下的方法有:事件、信号量、互斥量。同步速度较慢,但适用性比较好。

    **临界区:**通过对多线程的串行化来访问公共资源或一段代码、速度快,适合控制数据访问。

    **互斥量:**为协调共同对一个共享资源的单独访问而设计的。

    **信号量:**为控制一个具有有限数量用户资源而设计的。

    **事件:**用来通知线程有一些时间已发生,从而启动后继任务的开始。

    1 atomic

    atomic<int> num{0};
    int main(int, char **)
    {
        std::cout << "========boot=======" << endl;
    
        //检查是否无锁的
        std::cout << "num.is_lock_free():" << num.is_lock_free() << endl;
    
        num.store(10);                             //存储值
        std::cout << "num:" << num.load() << endl; //读取值
    
        int a = num.exchange(100); //交换值,返回原来的值
        std::cout << "num:" << num.load() << endl;
    
        std::cout << "========over=======" << endl;
    }
    

    atomic并不能保证类型T是无锁的,另外不同平台的处理器处理方式不同,也不能保证必定无锁,所以该类型都会有is_lock_free() 函数来判断是否无锁。

    有一个比较特殊的原子类型是atomic_flag,因为atomic_flag与其他原子类型不同,它是无锁的,即线程对其访问不需要加锁,而其他的原子类型不一定是无锁的。

    atomic_flag flag = ATOMIC_FLAG_INIT; //初始化
    int main(int, char **)
    {
        std::cout << "========boot=======" << endl;
    
        //之前还未设置标志,所以调用的时候返回false,然后设置了标志
        std::cout << "ret:" << flag.test_and_set() << endl;
        //再次调用的时候已经设置过标志,所以返回true
        std::cout << "ret:" << flag.test_and_set() << endl;
    
        flag.clear(); //清除标志
        //清除标志后,返回值为false,并且设置了标志
        std::cout << "ret:" << flag.test_and_set() << endl;
        //设置标志后,返回值为true
        std::cout << "ret:" << flag.test_and_set() << endl;
        std::cout << "========over=======" << endl;
    }
    

    2 临界区

    在Linux平台下,没有临界区的概念。

    #include <windows.h>
    CRITICAL_SECTION cs; //定义临界区对象
    void foo()
    {
        EnterCriticalSection(&cs);
        /* code 公共资源代码*/
        LeaveCriticalSection(&cs);
    }
    
    int main(int, char **)
    {
        std::cout << "========boot=======" << endl;
        InitializeCriticalSection(&cs); //初始化临界区
        thread th(foo);
        thread th(foo);
        InitializeCriticalSection(&cs); //初始化临界区
        std::cout << "========over=======" << endl;
    }
    

    3 互斥量

    在C++11中被命名为Mutex,所有其相关的类和函数都在头文件mutex中。一共有四种互斥元类,分别是:

    • **std::mutex;**最基本的互斥元类
    • **std::recursive_mutex;**递归Mutex类(同一线程可以对互斥量多次上锁,来获得对互斥量对象的多层所有权)。
    • **std::timed_mutex;**定时Mutex类。
    • **std::recursive_timed_mutex;**定时递归Mutex类

    上述四种互斥量元类都有一个成员函数lock和unlock来实现锁定与解锁的操作。

    除此之外,还有两种lock类,分别是:

    • **std::lock_guard;**这个类的使用类似智能指针,可以销毁时自动解锁;
    • **std::unique_lock;**这个类与1用法相同,但提供了更灵活的上锁和解锁控制,同时也更占资源。

    3.1 std::mutex类

    • 构造函数,std::mutex不允许拷贝构造,也不允许 move 拷贝,最初产生的 mutex 对象是处于 unlocked 状态的。
    • lock(),调用线程将锁住该互斥量。线程调用该函数会发生下面3 种情况:
      1. 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,该线程一直拥有该锁。
      2. 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住。
      3. 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。
    • unlock(), 解锁,释放对互斥量的所有权。
    • try_lock(),尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻塞。线程调用该函数也会出现下面3 种情况:
      1. 如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用 unlock 释放互斥量。
      2. 如果当前互斥量被其他线程锁住,则当前调用线程返回 false,而并不会被阻塞掉。
      3. 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。
    void foo()
    {
        if(mtx.try_lock())
        {
             /*公共资源code*/
             mtx.unlock();
        }
    }
    #include <mutex>
    std::mutex mtx;
    int main(int, char **)
    {
        std::cout << "========boot=======" << endl;
        thread th1(foo);
        thread th2(foo);
        th1.join();
        th2.join();
        std::cout << "========over=======" << endl;
    }
    

    3.2 lock_guard

    #include <mutex>
    std::mutex mtx;
    void foo()
    {
        //使用mutex对象定义一个局部的lock_guard对象
        std::lock_guard<std::mutex> lock(mtx);
        /*公共资源code*/
        //只有等到该lock_guard对象销毁后才能解锁
    }
    
    int main(int, char **)
    {
        std::cout << "========boot=======" << endl;
        thread th1(foo);
        thread th2(foo);
        th1.join();
        th2.join();
        std::cout << "========over=======" << endl;
    }
    

    3.3 unique_lock

    lock_guard本身并没有提供枷锁和解锁的接口,智能保证再析构的时候执行解锁操作,不够灵活。

    但是unique_lock提供了lock()和unlock()接口,能记录现在处于上锁还是解锁状态,在析构的时候,会根据当前状态来决定是否需要解锁。然而这是有代价的,因为它内部需要维护锁的状态,所以效率要比lock_guard低一点,在lock_guard能解决问题的时候,就用lock_guard,反之使用unique_lock。

    #include <mutex>
    std::mutex mtx;
    void foo()
    {
        //使用mutex对象定义一个局部的lock_guard对象
        std::unique_lock<std::mutex> lock(mtx);
        /*do something1 code*/
    
        lock.unlock();
        /*do others */
    
        lock.lock();
        /*do something2 code*/
    }
    
    int main(int, char **)
    {
        std::cout << "========boot=======" << endl;
        thread th1(foo);
        thread th2(foo);
        th1.join();
        th2.join();
        std::cout << "========over=======" << endl;
    }
    

    3.4 std::condition_variable

    condition_variable是一个类,搭配互斥量mutex来用,这个类主要有wait函数和notify函数。程序运行到wait函数的时候会先在此阻塞,然后自动unlock,那么其他线程在拿到锁以后就会往下运行,当运行到notify函数的时候,就会唤醒wait函数,然后自动lock并继续运行。

    当然wait函数还有第二个参数,这个参数接收一个布尔类型的值,当这个布尔类型的值为false的时候线程就会被阻塞在这里,只有当该线程被唤醒之后,且第二参数为true才会往下运行。

    notify_one函数每次只能唤醒一个线程,那么notify_all函数的作用就是可以唤醒所有的线程,但是最终能抢夺锁的只有一个线程,或者说有多个线程在wait,但是用notify_one去唤醒其中一个线程,那么这些线程就出现了去争夺互斥量的一个情况,那么最终没有获得锁的控制权的线程就会再次回到阻塞的状态,那么对于这些没有抢到控制权的这个过程就叫做虚假唤醒。那么对于虚假唤醒的解决方法就是加一个while循环。

    #include <mutex>
    #include <cstdlib>
    std::mutex mtx;
    std::condition_variable cv;
    std::queue<int> que;
    
    void consumer()
    {
        while (true)
        {
            std::unique_lock<std::mutex> lck(mtx);
            // while (que.size() == 0) //当队列为空的时候,需要等待
            // {
            //     cv.wait(lck);
            // }
            /*上述写法这样也可以。当队列不为空且线程被唤醒才可以继续执行。
            cv.wait(lck, []()
                    { return que.size() != 0; });
            */
            int temp = que.front();
            std::cout << "read the first element:" << temp << " from the queue" << endl;
            que.pop();
        }
    }
    
    void producer()
    {
        srand((int)time(0)); //随机种子
        while (true)
        {
            {
                std::unique_lock<std::mutex> lck(mtx);
                int temp = rand() % 100;
                que.push(temp);
                std::cout << "write an element:" << temp << " to the queue" << endl;
            }//注意这个作用域,为了就是unlock,不可以省。
            //然后再去唤醒。
            cv.notify_all();
        }
    }
    
    int main(int, char **)
    {
        std::cout << "========boot=======" << endl;
        thread th1(producer);
        thread th2(consumer);
        th1.join();
        th2.join();
        std::cout << "========over=======" << endl;
    }
    

    4 信号量(Semaphore)

    定义于头文件<semaphore.h>,信号量是一种轻量的同步机制,用于制约对共享资源的并发访问(控制线程的并发数量)。在可以使用两者时,信号量能比条件变量更有效率。

    // 初始化
    #include<semaphore.h>
    int sem_init(sem_t *sem,int pshared,unsigned int value);
    /*
    功能 创建一个信号量并初始化它的值,一个无名信号量在被使用前必须初始化
    参数 sem 信号量地址  
         pshared  等于0 信号量在线程间共享   不等于0 信号在进程间共享
         value 信号量的初始值
    返回 成功 0    失败 -1
    
    */
    
    
    // 销毁
    #include<semaphore,h>
    int sem_destroy(sem_t *sem);
    /*
    功能 删除sem标识的信号量
    参数 sem 信号量地址
    返回 成功 0    失败 -1
    */
    
    
    // P操作(减1)
    #include<semaphore.h>
    int sem_wait(sem_t *sem);
    /*
    功能 将信号量的值减1,操作前,先检查信号量(sem)的值是否为0,若为0,则阻塞,直到信号量大于0再减
    参数 sem 信号量地址
    返回 成功 0   失败 -1
    */
    
    // 非阻塞减1
    int sem_trywait(sem_t *sem);
    // 以非阻塞的方式来对信号量进行减1操作
    // 若操作前,信号量的值等于0,则对信号量的操作失败,函数立即返回
    
    // 限时减1
    int sem_timedwait(sem_t *sem,const struct timespec *abs_timeout);
    // 限时尝试将信号量的值减1
    // abs_timeout 绝对时间
    
    
    // V操作(加1)
    #include<semaphore.h>
    int sem_post(sem_t *sem);
    /*
    功能 将信号量的值加1,并发出信号唤醒等待线程(sem_wait());
    参数 sem 信号量地址
    返回 成功 0    失败 -1
    */
    
    
    // 获取信号量的值
    #include<semaphore.h>
    int sem_getvalue(sem_t *sem,int *val);
    /*
    功能 获取sem标识的信号量的值,保存在val中
    参数 sem 信号量地址   val 保存信号量值的地址
    返回 成功 0     失败 -1
    */
    
    //在两个函数中按顺序打印奇数偶数
    #include <semaphore.h>
    sem_t oddSem;
    sem_t evenSem;
    
    void getOdd()
    {
        for (size_t i = 0; i < 100; i++)
        {
            if (i % 2 != 0)
            {
                sem_wait(&oddSem);//-1 操作 如果操作前为0则阻塞。
                std::cout << "threadID:" << this_thread::get_id() << " getOdd:" << i << endl;
                sem_post(&evenSem);//+1操作 通知even打印。
            }
        }
    }
    
    void getEven()
    {
        for (size_t i = 0; i < 100; i++)
        {
            if (i % 2 == 0)
            {
                sem_wait(&evenSem);//-1操作,如果操作前为0则阻塞。
                std::cout << "threadID:" << this_thread::get_id() << " getEven:" << i << endl;
                sem_post(&oddSem);//+1操作,通知odd打印
            }
        }
    }
    
    int main(int, char **)
    {
        std::cout << "========boot=======" << endl;
    
        sem_init(&oddSem,0,0);
        sem_init(&evenSem,0,1);//从0开始打印,所以初始值为1.
    
        thread th1(getOdd);
        thread th2(getEven);
        th1.join();
        th2.join();
        std::cout << "========over=======" << endl;
    }
    

    5 事件

    HANDLE event = NULL;
    
    void m_raise()
    {
        for (size_t i = 0; i < 10; i++)
        {
            /* code */
            std::cout << "threadID:" << this_thread::get_id() <<__FUNCTION__<< i << endl;
            if(i==5)
            {
                //激活事件。
                SetEvent(event);
            }
        }
    }
    
    void receive()
    {
        //如果事件为激活状态则直接执行。
        //否则阻塞直到事件被激活。
        WaitForSingleObject(event,INFINITE);
        for (size_t i = 0; i < 10; i++)
        {
            /* code */
            std::cout << "threadID:" << this_thread::get_id() <<__FUNCTION__<< i << endl;
        }
    }
    
    int main(int, char **)
    {
        std::cout << "========boot=======" << endl;
        //初始化事件
        event = CreateEvent(NULL, FALSE, TRUE, NULL);  
    	ResetEvent(event);//设置事件状态为未激活状态。
    
        thread th1(receive);
        thread th2(m_raise);
        th1.join();
        th2.join();
        std::cout << "========over=======" << endl;
    }
    
    更多相关内容
  • 一文搞定c++多线程同步机制

    千次阅读 多人点赞 2020-09-08 21:02:05
    c++多线程同步机制 同步与互斥 现代操作系统都是多任务操作系统,通常同一时刻有大量可执行实体,则运行着的大量任务可能需要访问或使用同一资源,或者说这些任务之间具有依赖性。 线程同步:线程同步是指线程之间...

    c++多线程同步机制

    前序文章:一文搞定c++多线程

    同步与互斥

    现代操作系统都是多任务操作系统,通常同一时刻有大量可执行实体,则运行着的大量任务可能需要访问或使用同一资源,或者说这些任务之间具有依赖性。

    • 线程同步:线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。例如:两个线程A和B在运行过程中协同步调,按预定的先后次序运行,比如 A 任务的运行依赖于 B 任务产生的数据。
    • 线程互斥:线程互斥是指对于共享的操作系统资源,在各线程访问时具有排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许有限的线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。例如:两个线程A和B在运行过程中共享同一变量,但为了保持变量的一致性,如果A占有了该资源则B需要等待A释放才行,如果B占有了该资源需要等待B释放才行。

    为什么需要线程同步

    ​ 由于现在操作系统支持多个线程运行,可能多个线程之间会共享同一资源。当多个线程去访问同一资源时,如果不加以干预,可能会引起冲突。例如,多个线程同时访问同一个全局变量,如果都是读取操作,则不会出现问题。如果一个线程负责改变此变量的值,而其他线程负责同时读取变量内容,则不能保证读取到的数据是经过写线程修改后的。为了确保读线程读取到的是经过修改的变量,就必须在向变量写入数据时禁止其他线程对其的任何访问,直至赋值过程结束后再解除对其他线程的访问限制。这种保证线程能了解其他线程任务处理结束后的处理结果而采取的保护措施即为线程同步。

    ​ 举个例子,现在你银行卡里有100元,然后一个线程去执行消费,一个线程去执行充值,如果不加以干预,则可能出现这样的情况:消费的线程读取到你的卡里有100元,然后由于线程切换保存了当前的状态就去执行充值线程,充值线程完成充值后你的卡里实际上应该是10000元,然后切换到消费进程,消费进程由于已经读取过卡里的钱所以会直接进行之后的操作,完成后计算得到卡里的钱应该改为50,这便会将你真实的卡里的钱改成50,这当然是我们不希望看到的!如果进行了线程同步操作,当消费线程进行时,由于这是对数据进行写的操作,那么其他充值线程都需要被阻塞直至消费进程结束占据资源,这样便不会导致数据的不一致。

    ​ 举个代码例子,两个线程对一个共享数据进行++操作并且输出出来,代码如下:

    #include <iostream>
    #include <thread>
    #include <Windows.h>
    using namespace std;
    int share = 0;  //共享变量
    void thread1()
    {
        while(share<20)
        {
            share++;
            cout << "this is thread1! share is " << share << endl;
            Sleep(100);
        }
    }
    void thread2()
    {
        while (share < 20)
        {
            share++;
            cout << "this is thread2! share is " << share << endl;
            Sleep(100);
        }
    }
    
    int main()
    {
        thread task1(thread1); 
        thread task2(thread2); 
        task1.join();
        task2.join();
        cout << "main thread!" << endl;
    }   
    

    某一次的运行结果:

    PS D:\vscode_c> ./test
    this is thread1! share is 2
    this is thread2! share is 2
    this is thread1! share is 3
    this is thread2! share is 4
    this is thread1! share is this is thread2! share is 6
    6
    this is thread2! share is 8
    this is thread1! share is 8
    this is thread2! share is 9
    this is thread1! share is 10
    this is thread1! share is 12
    this is thread2! share is 13
    this is thread1! share is 14
    this is thread2! share is 15
    this is thread1! share is 16
    this is thread2! share is this is thread1! share is 18
    18
    this is thread1! share is 20
    this is thread2! share is 20
    main thread!
    

    可以看到,不但出现两个线程读取的变量值一样的现象(我们当然期望的是每一行都是一个唯一的数字并且有一个换行),还出现了cout的内容包括数字和换行符位置也有些错乱。主要原因是share和cout的缓冲区是thread1和thread2共享的,由于两个线程同时运行,便可能将一个已经修改的值读取,或者将另一个线程已经读取但是未修改的值进行读取,还有可能将另一个线程已经放入缓冲区的内容输出。当然,这个输出是不符合我们预期的!

    mutex互斥锁

    互斥锁是一种简单的通过加锁的方式控制多个线程对共享资源的访问,互斥锁有两个状态,上锁与解锁(lock和unlock)。lock互斥锁是一个原子操作,这说明在同一时刻只能有一个线程锁住互斥锁,不会出现同时上锁的情况,同时,互斥锁具有唯一性,一旦上锁,其他线程不能够再将其锁住。当一个互斥锁被锁住时,其他希望锁住该锁的线程将被挂起,直至该互斥锁被unlock解开,则这些线程将被唤醒并其中一个将再次抢占成功。

    互斥锁的通常执行流程如下:

    • 在访问共享资源的临界区前,将互斥锁锁住 lock
    • 在完成访问共享资源的操作后,将互斥锁unlock
    • 这期间,其他线程如果需要访问共享资源将调用lock,将自身挂起,直至该互斥锁被unlock才行

    利用互斥锁修改上面的代码:

    #include <iostream>
    #include <thread>
    #include <Windows.h>
    #include<mutex>
    using namespace std;
    mutex mut;
    int share = 0;
    void thread1()
    {
        while(share<20)
        {
            mut.lock();   //将互斥锁进行lock
            share++;
            cout << "this is thread1! share is " << share << endl;
            Sleep(100);
            mut.unlock();  //unlock 解开互斥锁
        }
    }
    void thread2()
    {
        while (share < 20)
        {
            mut.lock();   //将互斥锁进行lock
            share++;
            cout << "this is thread2! share is " << share << endl;
            Sleep(100);
            mut.unlock();  //unlock 解开互斥锁
        }
    }
    
    int main()
    {
        thread task1(thread1); 
        thread task2(thread2); 
        task1.join();
        task2.join();
        cout << "main thread!" << endl;
    }   
    

    查看运行结果:

    PS D:\vscode_c> ./test                
    this is thread1! share is 1
    this is thread2! share is 2
    this is thread1! share is 3
    this is thread2! share is 4
    this is thread2! share is 5
    this is thread2! share is 6
    this is thread2! share is 7
    this is thread2! share is 8
    this is thread2! share is 9
    this is thread1! share is 10
    this is thread2! share is 11
    this is thread1! share is 12
    this is thread1! share is 13
    this is thread1! share is 14
    this is thread2! share is 15
    this is thread2! share is 16
    this is thread2! share is 17
    this is thread1! share is 18
    this is thread1! share is 19
    this is thread2! share is 20
    main thread!
    

    很显然,这是符合我们预期的。不过事情总要做到更好,这个方法有什么问题呢?试想一下,如果在lock和unlock之间发生了异常,则可能永远不会执行到unlock,另一个进程将永远被挂起在那里等待。

    为了解决该问题,根据对象的析构函数自动调用的原理,c++11推出了std::lock_guard自动释放锁,其原理是:声明一个局部的lock_guard对象,在其构造函数中进行加锁,在其析构函数中进行解锁。最终的结果就是:在定义该局部对象的时候加锁(调用构造函数),出了该对象作用域的时候解锁(调用析构函数)。

    在C++中,通过构造std::mutex的实例来创建互斥元,可通过调用其成员函数lock()和unlock()来实现加锁和解锁,然后这是不推荐的做法,因为这要求程序员在离开函数的每条代码路径上都调用unlock(),包括由于异常所导致的在内。作为替代,标准库提供了std::lock_guard类模板,实现了互斥元的RAII惯用语法(资源获取即初始化)。该对象在构造时锁定所给的互斥元,析构时解锁该互斥元,从而保证被锁定的互斥元始终被正确解锁

    #include <iostream>
    #include <thread>
    #include <Windows.h>
    #include<mutex>
    using namespace std;
    mutex mut;
    int share = 0;
    void thread1()
    {
        while(share<20)
        {
            std::lock_guard<std::mutex> mtx_locker(mut);  //用lock_guard实现互斥锁
            if(share>=20)
                break;
            share++;
            cout << "this is thread1! share is " << share << endl;
            Sleep(100);
        }
    }
    void thread2()
    {
        while (share < 20)
        {
                std::lock_guard<std::mutex> mtx_locker(mut);  //用lock_guard实现互斥锁
                if (share >= 20)
                    break;
                share++;
                cout << "this is thread2! share is " << share << endl;
                Sleep(100);
        }
    }
    
    int main()
    {
        thread task1(thread1); 
        thread task2(thread2); 
        task1.join();
        task2.join();
        cout << "main thread!" << endl;
    }   
    

    运行结果:

    PS D:\vscode_c> ./test                
    this is thread1! share is 1
    this is thread2! share is 2
    this is thread2! share is 3
    this is thread2! share is 4
    this is thread2! share is 5
    this is thread2! share is 6
    this is thread2! share is 7
    this is thread2! share is 8
    this is thread2! share is 9
    this is thread2! share is 10
    this is thread2! share is 11
    this is thread1! share is 12
    this is thread2! share is 13
    this is thread2! share is 14
    this is thread2! share is 15
    this is thread2! share is 16
    this is thread1! share is 17
    this is thread2! share is 18
    this is thread2! share is 19
    this is thread1! share is 20
    main thread!
    

    win32的四种同步方式

    临界区

    临界区 (Critical Section) 是一段独占对某些共享资源访问的代码,在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。

    临界区在使用时以CRITICAL_SECTION结构对象保护共享资源,并分别用 EnterCriticalSection()和LeaveCriticalSection() 函数去标识和释放一个临界区。所用到的CRITICAL_SECTION结构对象必须经过InitializeCriticalSection() 的初始化后才能使用,而且必须确保所有线程中的任何试图访问此共享资源的代码都处在此临界区的保护之下。否则临界区将不会起到应有的作用,共享资源依然有被破坏的可能。

    临界区的使用:

    • 定义一个CRITICAL_SECTION类型的变量

    • 调用InitializeCriticalSection函数对变量进行初始化,函数的作用是初始化临界区,唯一的参数是指向结构体CRITICAL_SECTION的指针变量

      VOID InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection )
      
    • 为了将某段代码设置为临界区,在进入这段代码前调用EnterCriticalSection函数。该函数的作用是使调用该函数的线程进入已经初始化的临界区,并拥有该临界区的所有权。这是一个阻塞函数,如果线程获得临界区的所有权成功,则该函数将返回,调用线程继续执行,否则该函数将一直等待,这样会造成该函数的调用线程也一直等待。如果不想让调用线程等待(非阻塞),则应该使用TryEnterCriticalSection函数

      VOID WINAPI EnterCriticalSection(__inout LPCRITICAL_SECTION lpCriticalSection);
      
    • 在临界区代码后,需要调用LeaveCriticalSection函数。该函数的作用是使调用该函数的线程离开临界区并释放对该临界区的所有权,以便让其他线程也获得访问该共享资源的机会

      void WINAPI LeaveCriticalSection( _Inout_LPCRITICAL_SECTION lpCriticalSection);
      

      如果一个线程在进入临界区后没有调用LeaveCriticalSection,则会出现等待进入临界区的线程无限期等待的问题

    • 最后释放掉CRITICAL_SECTION结构指针,该函数的作用是删除程序中已经被初始化的临界区。如果函数调用成功,则程序会将内存中的临界区删除,防止出现内存错误。

      void WINAPI DeleteCriticalSection(_Inout_ LPCRITICAL_SECTION lpCriticalSection);
      

    tips:单进程的线程可以使用临界资源对象来解决同步互斥问题,该对象不能保证哪个线程能够获得到临界资源对象,因而该系统能公平的对待每一个线程

    利用临界区解决上面的问题:

    #include <iostream>
    #include <thread>
    #include <windows.h>
    #include<mutex>
    using namespace std;
    CRITICAL_SECTION Critical; //定义临界区句柄
    int share = 0;
    void thread1()
    {
        while(share<20)
        {
            EnterCriticalSection(&Critical);
            if(share>=20)
                break;
            share++;
            cout << "this is thread1! share is " << share << endl;
            Sleep(100);
            LeaveCriticalSection(&Critical);
        }
    }
    void thread2()
    {
        while (share < 20)
        {
            EnterCriticalSection(&Critical);
            if (share >= 20)
                break;
            share++;
            cout << "this is thread2! share is " << share << endl;
            Sleep(100);
            LeaveCriticalSection(&Critical);
        }
    }
    
    int main()
    {
        InitializeCriticalSection(&Critical); //初始化临界区对象
        thread task1(thread1); 
        thread task2(thread2); 
        task1.join();
        task2.join();
        cout << "main thread!" << endl;
    }   
    

    写起来和mutex类似,主要注意 的是一定要先初始化临界区对象。

    运行结果:

    PS D:\vscode_c> ./test                
    this is thread1! share is 1
    this is thread1! share is 2
    this is thread1! share is 3
    this is thread1! share is 4
    this is thread1! share is 5
    this is thread1! share is 6
    this is thread2! share is 7
    this is thread2! share is 8
    this is thread2! share is 9
    this is thread1! share is 10
    this is thread1! share is 11
    this is thread1! share is 12
    this is thread1! share is 13
    this is thread1! share is 14
    this is thread1! share is 15
    this is thread1! share is 16
    this is thread1! share is 17
    this is thread1! share is 18
    this is thread1! share is 19
    this is thread1! share is 20
    main thread!
    
    事件

    事件(Event)是WIN32提供的最灵活的线程间同步方式,事件可以处于激发状态或未激发状态。应用时,通过使用 CreateEvent 函数创建事件,然后使用信号控制线程运行。其中将事件变为有信号可使用 SetEvent 函数,将事件信号复位(变为无信号)可使用 ResetEvent 函数,信号可以配合 WaitForSingleObject 函数对线程的同步进行控制,当有信号时,此函数便会放行;无信号时,此函数会将阻塞。

    根据状态变迁方式的不同,事件可分为两类:
    (1)手动设置:这种对象只可能用程序手动设置,在需要该事件或者事件发生时,采用SetEvent及ResetEvent来进行设置。
    (2)自动恢复:一旦事件发生并被处理后,自动恢复到没有事件状态,不需要再次设置。

    相关的函数:

    函数名函数说明
    CreateEventCreates or opens a named or unnamed event object.
    CreateEventExCreates or opens a named or unnamed event object and returns a handle to the object.
    OpenEventOpens an existing named event object.
    PulseEventSets the specified event object to the signaled state and then resets it to the nonsignaled state after releasing the appropriate number of waiting threads.
    ResetEventSets the specified event object to the nonsignaled state.
    SetEventSets the specified event object to the signaled state.

    CreateEvent用于创建事件对象,函数原型为:

    HANDLE WINAPI CreateEvent(
      _In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes,
      _In_     BOOL                  bManualReset,
      _In_     BOOL                  bInitialState,  
      _In_opt_ LPCTSTR               lpName
    );
    

    着重强调一下第二个参数,CreateEvent的第二个参数 bManualReset 表示指定将事件对象创建成手动复原还是自动复原,如果是TRUE,那么必须用ResetEvent函数来手工将事件的状态复原到无信号状态。如果设置为FALSE,当事件被一个等待线程释放以后,系统将会自动将事件状态复原为无信号状态。第三个参数bInitialState 表示事件对象的初始状态。如果为true,则表示该事件对象初始时为有信号状态

    利用事件解决上面的问题:

    #include <iostream>
    #include <thread>
    #include <windows.h>
    #include<mutex>
    using namespace std;
    HANDLE hEvent; //定义事件句柄
    int share = 0;
    void thread1()
    {
        while(share<20)
        {
            WaitForSingleObject(hEvent, INFINITE); //等待对象为有信号状态
            if(share>=20)
                break;
            share++;
            cout << "this is thread1! share is " << share << endl;
            Sleep(100);
            SetEvent(hEvent);  //将事件设置为有信号状态
        }
    }
    void thread2()
    {
        while (share < 20)
        {
            WaitForSingleObject(hEvent, INFINITE); //等待对象为有信号状态
            if (share >= 20)
                break;
            share++;
            cout << "this is thread2! share is " << share << endl;
            Sleep(100);
            SetEvent(hEvent);  //将事件设置为有信号状态
        }
    }
    
    int main()
    {
        hEvent = CreateEvent(NULL, FALSE, TRUE, "event");  //创建事件   是自动恢复状态
        thread task1(thread1); 
        thread task2(thread2); 
        task1.join();
        task2.join();
        cout << "main thread!" << endl;
    }   
    

    运行结果:

    PS D:\vscode_c> ./test                
    this is thread1! share is 1
    this is thread2! share is 2
    this is thread1! share is 3
    this is thread2! share is 4
    this is thread1! share is 5
    this is thread2! share is 6
    this is thread1! share is 7
    this is thread2! share is 8
    this is thread1! share is 9
    this is thread2! share is 10
    this is thread1! share is 11
    this is thread2! share is 12
    this is thread1! share is 13
    this is thread2! share is 14
    this is thread1! share is 15
    this is thread2! share is 16
    this is thread1! share is 17
    this is thread2! share is 18
    this is thread1! share is 19
    this is thread2! share is 20
    main thread!
    
    信号量

    信号量是维护0到指定最大值之间的同步对象,用于线程的同步或者限制线程运行的数量。信号量状态在其计数大于0时是有信号的,而其计数是0时是无信号的。信号量对象在控制上可以支持有限数量共享资源的访问。

    通常来说,信号量具有如下特点:

    • 如果当前资源的数量大于0,则信号量有效
    • 如果当前资源数量是0,则信号量无效
    • 当前资源的数量不能够为负值
    • 当前资源数量一定小于等于最大资源数量

    信号量相关的函数:

    //头文件
    #include <windows.h>
    
    //创建信号量API
    HANDLE WINAPI CreateSemaphore(
     _In_opt_ LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,//指向SECURITY_ATTRIBUTES的指针;
     _In_     LONG                  lInitialCount,          //信号量对象的初始值;
     _In_     LONG                  lMaximumCount,  //信号量对象的最大值,这个值必须大于0;
     _In_opt_ LPCTSTR               lpName                 //信号量对象的名称;
    );
    
    //等待信号量API
    DWORD WINAPI WaitForSingleObject(
      _In_ HANDLE hHandle,          //信号量对象句柄
      _In_ DWORD  dwMilliseconds    //等待信号量时间,INFINET代表永久等待;
    );
    
    //打开信号量
    HANDLE OpenSemaphore (
       DWORD fdwAccess,      //access
       BOOL bInherithandle,  //如果允许子进程继承句柄,则设为TRUE
       PCTSTR pszName  //指定要打开的对象的名字
      );
    
    //释放信号量句柄
    BOOL WINAPI ReleaseSemaphore(
      _In_      HANDLE hSemaphore,         //信号量对象句柄;
      _In_      LONG   lReleaseCount,      //信号量释放的值,必须大于0;
      _Out_opt_ LPLONG lpPreviousCount     //前一次信号量值的指针,不需要可置为空;
    );
    

    用信号量解决上面的问题:

    #include <iostream>
    #include <thread>
    #include <windows.h>
    #include<mutex>
    using namespace std;
    HANDLE hSemaphore; //定义信号量句柄
    int share = 0;
    void thread1()
    {
        while(share<20)
        {
            WaitForSingleObject(hSemaphore, INFINITE); //等待信号量为有信号状态
            if(share>=20)
                break;
            share++;
            cout << "this is thread1! share is " << share << endl;
            Sleep(100);
            ReleaseSemaphore(hSemaphore, 1, nullptr);  //释放信号量
        }
    }
    void thread2()
    {
        while (share < 20)
        {
            WaitForSingleObject(hSemaphore, INFINITE); //等待信号量为有信号状态
            if (share >= 20)
                break;
            share++;
            cout << "this is thread2! share is " << share << endl;
            Sleep(100);
            ReleaseSemaphore(hSemaphore, 1, nullptr); //释放信号量
        }
    }
    
    int main()
    {
        hSemaphore = CreateSemaphore(NULL, 1, 20, "semaphore"); //创建信号量
        thread task1(thread1); 
        thread task2(thread2); 
        task1.join();
        task2.join();
        cout << "main thread!" << endl;
    }   
    

    运行结果:

    PS D:\vscode_c> ./test                
    this is thread1! share is 1
    this is thread2! share is 2
    this is thread1! share is 3
    this is thread2! share is 4
    this is thread1! share is 5
    this is thread2! share is 6
    this is thread1! share is 7
    this is thread2! share is 8
    this is thread1! share is 9
    this is thread2! share is 10
    this is thread1! share is 11
    this is thread2! share is 12
    this is thread1! share is 13
    this is thread2! share is 14
    this is thread1! share is 15
    this is thread2! share is 16
    this is thread1! share is 17
    this is thread2! share is 18
    this is thread1! share is 19
    this is thread2! share is 20
    main thread!
    
    互斥量

    windows下提供有互斥对象机制。 只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程访问。互斥不仅能实现同一应用程序的公共资源安全共享,还能实现不同应用程序的公共资源安全共享。不过该互斥量和mutex基本一样,所以用移植性更好的mutex更好

    互斥量主要的函数:

    //创建互斥量
    HANDLE WINAPI CreateMutex(
      __in          LPSECURITY_ATTRIBUTES lpMutexAttributes,//互斥对象的安全属性
      __in          BOOL bInitialOwner,//互斥对象的初始状态;TRUE表示互斥对象的线程ID为当前调度线程的线程ID,当前创建互斥对象的线程具有他的拥有权,互斥对象的递归计数器为1
      __in          LPCTSTR lpName//互斥对象的名称,NULL表示创建一个匿名的互斥对象
    );
    //释放互斥量
    BOOL WINAPI ReleaseMutex(
      __in          HANDLE hMutex
    );
    //等待互斥量
    DWORD WINAPI WaitForSingleObject(
      __in          HANDLE hHandle,//等待内核对象句柄
      __in          DWORD dwMilliseconds//等待时间,INFINITE表示无限等待
    );
    

    用互斥量解决上面的问题:

    #include <iostream>
    #include <thread>
    #include <windows.h>
    #include<mutex>
    using namespace std;
    HANDLE hMutex; //定义互斥对象句柄
    int share = 0;
    void thread1()
    {
        while(share<20)
        {
            WaitForSingleObject(hMutex, INFINITE);  //等待互斥量
            if(share>=20)
                break;
            share++;
            cout << "this is thread1! share is " << share << endl;
            Sleep(100);
            ReleaseMutex(hMutex);  //释放互斥量
        }
    }
    void thread2()
    {
        while (share < 20)
        {
            WaitForSingleObject(hMutex, INFINITE); //等待互斥量
            if (share >= 20)
                break;
            share++;
            cout << "this is thread2! share is " << share << endl;
            Sleep(100);
            ReleaseMutex(hMutex); //释放互斥量
        }
    }
    
    int main()
    {
        hMutex = CreateMutex(NULL, false, "mutex"); //创建互斥对象
        thread task1(thread1); 
        thread task2(thread2); 
        task1.join();
        task2.join();
        cout << "main thread!" << endl;
    }   
    

    运行结果:

    PS D:\vscode_c> ./test                
    this is thread1! share is 1
    this is thread2! share is 2
    this is thread1! share is 3
    this is thread2! share is 4
    this is thread1! share is 5
    this is thread2! share is 6
    this is thread1! share is 7
    this is thread2! share is 8
    this is thread1! share is 9
    this is thread2! share is 10
    this is thread1! share is 11
    this is thread2! share is 12
    this is thread1! share is 13
    this is thread2! share is 14
    this is thread1! share is 15
    this is thread2! share is 16
    this is thread1! share is 17
    this is thread2! share is 18
    this is thread1! share is 19
    this is thread2! share is 20
    main thread!
    
    展开全文
  • C++多线程同步的几种方式

    千次阅读 2019-12-26 20:25:35
    文章目录Overviewmutexlock_guardunique_lockcondition_variablefuturepromisepackaged_...C++多线程同步方式有这么几种: mutex lock_guard unique_lock condition_variable future promise packaged...

    Overview

    C++的多线程同步方式有这么几种:

    • mutex
      • lock_guard
      • unique_lock
    • condition_variable
    • future
      • promise
      • packaged_task
      • async

    C++11并没有提供semaphore的API,信号量太容易出错了(too error prone),通过组合互斥锁(mutex)和条件变量(condition variable)可以达到相同的效果,且更加安全。

    mutex

    官网介绍:mutex
    它包含以下三个部分:

    • Mutex type
    • Locks:lock_guardunique_lock
    • Functions:try_locklock

    example:
    mutex的实现也很简单,在进入临界区之前调用该变量(mtx)的lock函数,出临界区之前调用该变量的(mtx)的unlock函数。所以程序会连续输出50个'*'或者连续输出50个'$',而不会'*' '$'交替输出。

    但是考虑这样一个问题,std::cout << '\n',这里发生异常会发生什么?抛出异常后,意味着mtx.unlock()不会被执行,即锁没有被释放,整个程序进入不了临界区,该程序往往会挂死。

    // mutex example
    #include <iostream>       // std::cout
    #include <thread>         // std::thread
    #include <mutex>          // std::mutex
    
    std::mutex mtx;           // mutex for critical section
    
    void print_block (int n, char c) {
      // critical section (exclusive access to std::cout signaled by locking mtx):
      mtx.lock();
      for (int i=0; i<n; ++i) { std::cout << c; }
      std::cout << '\n';
      mtx.unlock();
    }
    
    int main ()
    {
      std::thread th1 (print_block,50,'*');
      std::thread th2 (print_block,50,'$');
    
      th1.join();
      th2.join();
    
      return 0;
    }
    

    Possible output (order of lines may vary, but characters are never mixed):

    **************************************************
    $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
    

    lock_guard

    在构造时,互斥对象被调用线程锁定,而在销毁时,互斥对象被解锁。它是最简单的锁,作为具有自动持续时间的对象特别有用,该持续时间一直持续到其上下文结束。这样,可以保证在抛出异常的情况下互斥对象已正确解锁。

    example

    // lock_guard example
    #include <iostream>       // std::cout
    #include <thread>         // std::thread
    #include <mutex>          // std::mutex, std::lock_guard
    #include <stdexcept>      // std::logic_error
    
    std::mutex mtx;
    
    void print_even (int x) {
      if (x%2==0) std::cout << x << " is even\n";
      else throw (std::logic_error("not even"));
    }
    
    void print_thread_id (int id) {
      try {
        // using a local lock_guard to lock mtx guarantees unlocking on destruction / exception:
        std::lock_guard<std::mutex> lck (mtx);
        print_even(id);
      }
      catch (std::logic_error&) {
        std::cout << "[exception caught]\n";
      }
    }
    
    int main ()
    {
      std::thread threads[10];
      // spawn 10 threads:
      for (int i=0; i<10; ++i)
        threads[i] = std::thread(print_thread_id,i+1);
    
      for (auto& th : threads) th.join();
    
      return 0;
    }
    

    possible output

    [exception caught]
    2 is even
    [exception caught]
    4 is even
    [exception caught]
    6 is even
    [exception caught]
    8 is even
    [exception caught]
    10 is even
    

    unique_lock

    unique_lock基本用法和lock_guard一致,在构造函数和析构函数中进行锁操作,不同的地方在于它提供了非常多构造函数。

    example

    // unique_lock example
    #include <iostream>       // std::cout
    #include <thread>         // std::thread
    #include <mutex>          // std::mutex, std::unique_lock
    
    std::mutex mtx;           // mutex for critical section
    
    void print_block (int n, char c) {
      // critical section (exclusive access to std::cout signaled by lifetime of lck):
      std::unique_lock<std::mutex> lck (mtx);
      for (int i=0; i<n; ++i) { std::cout << c; }
      std::cout << '\n';
    }
    
    int main ()
    {
      std::thread th1 (print_block,50,'*');
      std::thread th2 (print_block,50,'$');
    
      th1.join();
      th2.join();
    
      return 0;
    }
    

    condition_variable

    条件变量是一个对象,可以阻塞线程,直到被通知恢复。当调用其等待功能之一时,它使用unique_lock(通过互斥锁)来锁定线程。该线程将保持阻塞状态,直到被另一个在同一个condition_variable对象上调用通知功能的线程唤醒为止。
    Wait functions

    • wait
      • Wait until notified (public member function )
    • wait_for
      • Wait for timeout or until notified (public member function )
    • wait_until
      • Wait until notified or time point (public member function )

    Notify functions

    • notify_one
      • Notify one (public member function )
    • notify_all
      • Notify all (public member function )

    example

    // condition_variable::notify_one
    #include <iostream>           // std::cout
    #include <thread>             // std::thread
    #include <mutex>              // std::mutex, std::unique_lock
    #include <condition_variable> // std::condition_variable
    
    std::mutex mtx;
    std::condition_variable produce,consume;
    
    int cargo = 0;     // shared value by producers and consumers
    
    void consumer () {
      std::unique_lock<std::mutex> lck(mtx);
      while (cargo==0) consume.wait(lck);
      std::cout << cargo << '\n';
      cargo=0;
      produce.notify_one();
    }
    
    void producer (int id) {
      std::unique_lock<std::mutex> lck(mtx);
      while (cargo!=0) produce.wait(lck);
      cargo = id;
      consume.notify_one();
    }
    
    int main ()
    {
      std::thread consumers[10],producers[10];
      // spawn 10 consumers and 10 producers:
      for (int i=0; i<10; ++i) {
        consumers[i] = std::thread(consumer);
        producers[i] = std::thread(producer,i+1);
      }
    
      // join them back:
      for (int i=0; i<10; ++i) {
        producers[i].join();
        consumers[i].join();
      }
    
      return 0;
    }
    

    Possible output (order of consumed cargoes may vary):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    

    future

    future的目标是充分利用CPU的并发性,它只能通过asyncpromisepackage_task三种方式构造。future只能移动,不可复制,需要复制时可以使用shared_future,但通常不建议使用。调用future的get()时可能会发生阻塞,直到返回值ready。future有三种姿势的等待:

    • wait():一直等待直到得到返回值
    • wait_for():设定一个超时时间;
    • wait_until():等待到某个时间点。

    future有一特化版本future,返回值为空,即不返回任何值,因此仅能用于线程间通知,但却是最常用的future。

    promise

    promise对象可以通过调用成员get_future将此共享状态与future对象关联。调用之后,两个对象共享相同的共享状态:

    • promise:promise对象是异步提供者,在共享状态的时候设置一个值
    • future负责:future对象是异步返回对象,可以检索共享状态的值,并在必要时等待其准备就绪

    example

    // promise example
    #include <iostream>       // std::cout
    #include <functional>     // std::ref
    #include <thread>         // std::thread
    #include <future>         // std::promise, std::future
    
    void print_int (std::future<int>& fut) {
      int x = fut.get();
      std::cout << "value: " << x << '\n';
    }
    
    int main ()
    {
      std::promise<int> prom;                      // create promise
    
      std::future<int> fut = prom.get_future();    // engagement with future
    
      std::thread th1 (print_int, std::ref(fut));  // send future to new thread
    
      prom.set_value (10);                         // fulfill promise
                                                   // (synchronizes with getting the future)
      th1.join();
      return 0;
    }
    

    Output:

    value: 10
    

    packaged_task

    很多情况下并不希望另起一个线程,因为线程是非常重要的资源。因此希望可以合理的管理线程资源,这就需要使用线程池。如何将future与线程池同时使用呢?这就需要采用package_task。package_task本质是将一个函数包装成一个future。这个task类似于std::function,有输入输出,大家可以将其认为是一个异步函数,但该异步函数并不负责执行,而是将其结果预置于一个future变量中,然后交给一个线程来实际执行,此时主线程便可以得到其返回值。

    example

    // packaged_task example
    #include <iostream>     // std::cout
    #include <future>       // std::packaged_task, std::future
    #include <chrono>       // std::chrono::seconds
    #include <thread>       // std::thread, std::this_thread::sleep_for
    
    // count down taking a second for each value:
    int countdown (int from, int to) {
      for (int i=from; i!=to; --i) {
        std::cout << i << '\n';
        std::this_thread::sleep_for(std::chrono::seconds(1));
      }
      std::cout << "Lift off!\n";
      return from-to;
    }
    
    int main ()
    {
      std::packaged_task<int(int,int)> tsk (countdown);   // set up packaged_task
      std::future<int> ret = tsk.get_future();            // get future
    
      std::thread th (std::move(tsk),10,0);   // spawn thread to count down from 10 to 0
    
      // ...
    
      int value = ret.get();                  // wait for the task to finish and get result
    
      std::cout << "The countdown lasted for " << value << " seconds.\n";
    
      th.join();
    
      return 0;
    }
    

    Possible output:

    10
    9
    8
    7
    6
    5
    4
    3
    2
    1
    Lift off!
    The countdown lasted for 10 seconds.
    

    async

    有时某项工作很早就可以开始做(前置条件都已完备),而等待这件工作结果的任务在非常靠后的位置,这时候就需要async。换言之,如果可以尽早开始做一件事,就让其在后台运行即可,或快或慢都可以,只需在需要结果的时候运行完成就好。

    example

    // async example
    #include <iostream>       // std::cout
    #include <future>         // std::async, std::future
    
    // a non-optimized way of checking for prime numbers:
    bool is_prime (int x) {
      std::cout << "Calculating. Please, wait...\n";
      for (int i=2; i<x; ++i) if (x%i==0) return false;
      return true;
    }
    
    int main ()
    {
      // call is_prime(313222313) asynchronously:
      std::future<bool> fut = std::async (is_prime,313222313);
    
      std::cout << "Checking whether 313222313 is prime.\n";
      // ...
    
      bool ret = fut.get();      // waits for is_prime to return
    
      if (ret) std::cout << "It is prime!\n";
      else std::cout << "It is not prime.\n";
    
      return 0;
    }
    

    Possible output (the first two lines may be in a different order, or scrambled):

    Checking whether 313222313 is prime.
    Calculating. Please, wait...
    It is prime!
    

    Reference

    10分钟,带你掌握C++多线程同步!
    Multi-threading

    展开全文
  • 背景问题:在特定的应用场景下,多线程不进行同步会造成什么问题?通过多线程模拟多窗口售票为例:#include <iostream> #include<pthread.h> #include<stdio.h> #include<stdlib.h> #...

    背景问题:在特定的应用场景下,多线程不进行同步会造成什么问题?

    通过多线程模拟多窗口售票为例:

    #include <iostream>
    #include<pthread.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<unistd.h>
    
    using namespace std;
    
    int ticket_sum=20;
    void *sell_ticket(void *arg)
    {
        for(int i=0; i<20; i++)
        {
            if(ticket_sum>0)
            {
                sleep(1);
                cout<<"sell the "<<20-ticket_sum+1<<"th"<<endl;
                ticket_sum--;
            }
        }
        return 0;
    }
    
    int main()
    {
        int flag;
        pthread_t tids[4];
    
        for(int i=0; i<4; i++)
        {
            flag=pthread_create(&tids[i],NULL,&sell_ticket,NULL);
            if(flag)
            {
                cout<<"pthread create error ,flag="<<flag<<endl;
                return flag;
            }
        }
    
        sleep(20);
        void *ans;
        for(int i=0; i<4; i++)
        {
            flag=pthread_join(tids[i],&ans);
            if(flag)
            {
                cout<<"tid="<<tids[i]<<"join erro flag="<<flag<<endl;
                return flag;
            }
            cout<<"ans="<<ans<<endl;
        }
        return 0;
    }

    image_thumb4

    分析:总票数只有20张,却卖出了23张,是非常明显的超买超卖问题,而造成这个问题的根本原因就是同时发生的各个线程都可以对ticket_sum进行读取和写入!

    ps:

    1.在并发情况下,指令执行的先后顺序由内核决定,同一个线程内部,指令按照先后顺序执行,但不同线程之间的指令很难说清楚是哪一个先执行,如果运行的结果依赖于不同线程执行的先后的话,那么就会形成竞争条件,在这样的情况下,计算的结果很难预知,所以应该尽量避免竞争条件的形成

    2.最常见的解决竞争条件的方法是将原先分离的两个指令构成一个不可分割的原子操作,而其他任务不能插入到原子操作中!

    3.对多线程来说,同步指的是在一定时间内只允许某一个线程访问某个资源,而在此时间内,不允许其他线程访问该资源!

    4.线程同步的常见方法:互斥锁,条件变量,读写锁,信号量

    一.互斥锁

    本质就是一个特殊的全局变量,拥有lock和unlock两种状态,unlock的互斥锁可以由某个线程获得,一旦获得,这个互斥锁会锁上变成lock状态,此后只有该线程由权力打开该锁,其他线程想要获得互斥锁,必须得到互斥锁再次被打开之后

    采用互斥锁来同步资源:

    #include <iostream>
    #include<pthread.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<unistd.h>
    
    using namespace std;
    
    int ticket_sum=20;
    pthread_mutex_t mutex_x=PTHREAD_MUTEX_INITIALIZER;//static init mutex
    
    void *sell_ticket(void *arg)
    {
        for(int i=0; i<20; i++)
        {
            pthread_mutex_lock(&mutex_x);//atomic opreation through mutex lock
            if(ticket_sum>0)
            {
                sleep(1);
                cout<<"sell the "<<20-ticket_sum+1<<"th"<<endl;
                ticket_sum--;
            }
            pthread_mutex_unlock(&mutex_x);
        }
        return 0;
    }
    
    int main()
    {
        int flag;
        pthread_t tids[4];
    
        for(int i=0; i<4; i++)
        {
            flag=pthread_create(&tids[i],NULL,&sell_ticket,NULL);
            if(flag)
            {
                cout<<"pthread create error ,flag="<<flag<<endl;
                return flag;
            }
        }
    
        sleep(20);
        void *ans;
        for(int i=0; i<4; i++)
        {
            flag=pthread_join(tids[i],&ans);
            if(flag)
            {
                cout<<"tid="<<tids[i]<<"join erro flag="<<flag<<endl;
                return flag;
            }
            cout<<"ans="<<ans<<endl;
        }
        return 0;
    }

    image_thumb8

    分析:通过为售票的核心代码段加互斥锁使得其变成了一个原子性操作!不会被其他线程影响


    1.互斥锁的初始化

    互斥锁的初始化分为静态初始化和动态初始化

    静态:pthread_mutex_t mutex_x=PTHREAD_MUTEX_INITIALIZER;//static init mutex

    动态:pthread_mutex_init函数

    ps:互斥锁静态初始化和动态初始化的区别?

    待补充。。。。

    2.互斥锁的相关属性及分类

    //初始化互斥锁属性
    pthread_mutexattr_init(pthread_mutexattr_t attr);
    
    //销毁互斥锁属性
    pthread_mutexattr_destroy(pthread_mutexattr_t attr);
    
    //用于获取互斥锁属性
    int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr , int *restrict pshared);
    
    //用于设置互斥锁属性
    int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr , int pshared);

    attr表示互斥锁的属性

    pshared表示互斥锁的共享属性,由两种取值:

    1)PTHREAD_PROCESS_PRIVATE:锁只能用于一个进程内部的两个线程进行互斥(默认情况)

    2)PTHREAD_PROCESS_SHARED:锁可用于两个不同进程中的线程进行互斥,使用时还需要在进程共享内存中分配互斥锁,然后为该互斥锁指定属性就可以了


    互斥锁的分类:

    //获取互斥锁类型
    int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr , int *restrict type);
    
    //设置互斥锁类型
    int pthread_mutexattr_settype(const pthread_mutexattr_t *restrict attr , int type);

    参数type表示互斥锁的类型,总共有以下四种类型:

    1.PTHREAD_MUTEX_NOMAL:标准互斥锁,第一次上锁成功,第二次上锁会失败并阻塞

    2.PTHREAD_MUTEX_RECURSIVE:递归互斥锁,第一次上锁成功,第二次上锁还是会成功,可以理解为内部有一个计数器,每加一次锁计数器加1,解锁减1

    3.PTHREAD_MUTEX_ERRORCHECK:检查互斥锁,第一次上锁会成功,第二次上锁出错返回错误信息,不会阻塞

    4.PTHREAD_MUTEX_DEFAULT:默认互斥锁,第一次上锁会成功,第二次上锁会失败

    3,测试加锁函数

    int pthread_mutex_lock(&mutex):测试加锁函数在锁已经被占据时返回EBUSY而不是挂起等待,当然,如果锁没有被占领的话可以获得锁

    为了清楚的看到两个线程争用资源的情况,我们使得其中一个函数使用测试加锁函数进行加锁,而另外一个使用正常的加锁函数进行加锁

    #include <iostream>
    #include<pthread.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<unistd.h>
    #include<errno.h>
    using namespace std;
    
    int ticket_sum=20;
    pthread_mutex_t mutex_x=PTHREAD_MUTEX_INITIALIZER;//static init mutex
    
    void *sell_ticket_1(void *arg)
    {
        for(int i=0; i<20; i++)
        {
            pthread_mutex_lock(&mutex_x);
            if(ticket_sum>0)
            {
                sleep(1);
                cout<<"thread_1 sell the "<<20-ticket_sum+1<<"th ticket"<<endl;
                ticket_sum--;
            }
            sleep(1);
            pthread_mutex_unlock(&mutex_x);
            sleep(1);
        }
        return 0;
    }
    
    void *sell_ticket_2(void *arg)
    {
        int flag;
        for(int i=0; i<10; i++)
        {
            flag=pthread_mutex_trylock(&mutex_x);
            if(flag==EBUSY)
            {
                cout<<"sell_ticket_2:the variable is locked by sell_ticket_1"<<endl;
            }
            else if(flag==0)
            {
                if(ticket_sum>0)
                {
                    sleep(1);
                    cout<<"thread_2 sell the "<<20-ticket_sum+1<<"th tickets"<<endl;
                    ticket_sum--;
                }
                pthread_mutex_unlock(&mutex_x);
            }
            sleep(1);
        }
        return 0;
    }
    int main()
    {
        int flag;
        pthread_t tids[2];
    
        flag=pthread_create(&tids[0],NULL,&sell_ticket_1,NULL);
        if(flag)
        {
            cout<<"pthread create error ,flag="<<flag<<endl;
            return flag;
        }
    
        flag=pthread_create(&tids[1],NULL,&sell_ticket_2,NULL);
        if(flag)
        {
            cout<<"pthread create error ,flag="<<flag<<endl;
    
            return flag;
        }
    
        void *ans;
        sleep(30);
        flag=pthread_join(tids[0],&ans);
        if(flag)
        {
            cout<<"tid="<<tids[0]<<"join erro flag="<<flag<<endl;
            return flag;
        }
        else
        {
            cout<<"ans="<<ans<<endl;
        }
    
        flag=pthread_join(tids[1],&ans);
        if(flag)
        {
            cout<<"tid="<<tids[1]<<"join erro flag="<<flag<<endl;
            return flag;
        }
        else
        {
            cout<<"ans="<<ans<<endl;
        }
    
        return 0;
    }

    QQ20190714150711_thumb1

    分析:通过测试加锁函数我们可以清晰的看到两个线程争用资源的情况


    二.条件变量

    互斥量不是万能的,比如某个线程正在等待共享数据内某个条件出现,可可能需要重复对数据对象加锁和解锁(轮询),但是这样轮询非常耗费时间和资源,而且效率非常低,所以互斥锁不太适合这种情况

    我们需要这样一种方法:当线程在等待满足某些条件时使线程进入睡眠状态,一旦条件满足,就换线因等待满足特定条件而睡眠的线程

    如果我们能够实现这样一种方法,程序的效率无疑会大大提高,而这种方法正是条件变量!

    样例:

    #include <iostream>
    #include<pthread.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<unistd.h>
    #include<errno.h>
    using namespace std;
    
    pthread_cond_t qready=PTHREAD_COND_INITIALIZER;   //cond
    pthread_mutex_t qlock=PTHREAD_MUTEX_INITIALIZER;  //mutex
    
    int x=10,y=20;
    
    void *f1(void *arg)
    {
      cout<<"f1 start"<<endl;
      pthread_mutex_lock(&qlock);
      while(x<y)
      {
        pthread_cond_wait(&qready,&qlock);
      }
      pthread_mutex_unlock(&qlock);
      sleep(3);
      cout<<"f1 end"<<endl;
      return 0;
    }
    
    void *f2(void *arg)
    {
      cout<<"f2 start"<<endl;
      pthread_mutex_lock(&qlock);
      x=20;
      y=10;
      cout<<"has a change,x="<<x<<" y="<<y<<endl;
      pthread_mutex_unlock(&qlock);
      if(x>y)
      {
        pthread_cond_signal(&qready);
      }
      cout<<"f2 end"<<endl;
      return 0;
    }
    
    int main()
    {
      pthread_t tids[2];
      int flag;
    
      flag=pthread_create(&tids[0],NULL,f1,NULL);
      if(flag)
      {
        cout<<"pthread 1 create error "<<endl;
        return flag;
      }
    
      sleep(2);
    
      flag=pthread_create(&tids[1],NULL,f2,NULL);
      if(flag)
      {
        cout<<"pthread 2 create erro "<<endl;
        return flag;
      }
    
      sleep(5);
      return 0;
    }

    QQ20190714155203_thumb1

    分析:线程1不满足条件被阻塞,然后线程2运行,改变了条件,线程2发行条件改变了通知线程1运行,然后线程2结束,然后线程1继续运行,然后线程1结束,为了确保线程1先执行,在创建线程2之前我们sleep了2秒

    ps:

    1.条件变量通过运行线程阻塞和等待另一个线程发送信号的方法弥补互斥锁的不足,常常和互斥锁一起使用,使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开响应的互斥锁并等待条件发生变化,一旦其他的某个线程改变了条件变量,它将通知响应的条件变量换线一个或多个正被此条件变量阻塞的线程,这些线程将重新锁定互斥锁并且重新测试条件是否满足

    1.条件变量的相关函数

    1)创建

    静态方式:pthread_cond_t cond PTHREAD_COND_INITIALIZER

    动态方式:int pthread_cond_init(&cond,NULL)

    Linux thread 实现的条件变量不支持属性,所以NULL(cond_attr参数)

    2)注销

    int pthread_cond_destory(&cond)

    只有没有线程在该条件变量上,该条件变量才能注销,否则返回EBUSY

    因为Linux实现的条件变量没有分配什么资源,所以注销动作只包括检查是否有等待线程!(请参考条件变量的底层实现)

    3)等待

    条件等待:int pthread_cond_wait(&cond,&mutex)

    计时等待:int pthread_cond_timewait(&cond,&mutex,time)

    1.其中计时等待如果在给定时刻前条件没有被满足,则返回ETIMEOUT,结束等待

    2.无论那种等待方式,都必须有一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait形成竞争条件!

    3.在调用pthread_cond_wait前必须由本线程加锁

    4)激发

    激发一个等待线程:pthread_cond_signal(&cond)

    激发所有等待线程:pthread_cond_broadcast(&cond)

    重要的是,pthread_cond_signal不会存在惊群效应,也就是是它最多给一个等待线程发信号,不会给所有线程发信号唤醒提他们,然后要求他们自己去争抢资源!

    pthread_cond_signal会根据等待线程的优先级和等待时间来确定激发哪一个等待线程


    下面看一个程序,找到程序存在的问题

    #include <iostream>
    #include<pthread.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<unistd.h>
    #include<errno.h>
    using namespace std;
    
    pthread_cond_t taxi_cond=PTHREAD_COND_INITIALIZER; //taix arrive cond
    pthread_mutex_t taxi_mutex=PTHREAD_MUTEX_INITIALIZER;// sync mutex
    
    void *traveler_arrive(void *name)
    {
        cout<<"Traveler:"<<(char*)name<<" needs a taxi now!"<<endl;
        pthread_mutex_lock(&taxi_mutex);
        pthread_cond_wait(&taxi_cond,&taxi_mutex);
        pthread_mutex_unlock(&taxi_mutex);
        cout<<"Traveler:"<<(char*)name<<" now got a taxi!"<<endl;
        pthread_exit((void*)0);
    }
    
    void *taxi_arrive(void *name)
    {
        cout<<"Taxi:"<<(char*)name<<" arriver."<<endl;
        pthread_cond_signal(&taxi_cond);
        pthread_exit((void*)0);
    }
    
    int main()
    {
        pthread_t tids[3];
        int flag;
    
        flag=pthread_create(&tids[0],NULL,taxi_arrive,(void*)("Jack"));
        if(flag)
        {
            cout<<"pthread_create error:flag="<<flag<<endl;
            return flag;
        }
        cout<<"time passing by"<<endl;
        sleep(1);
    
        flag=pthread_create(&tids[1],NULL,traveler_arrive,(void*)("Susan"));
        if(flag)
        {
            cout<<"pthread_create error:flag="<<flag<<endl;
            return flag;
        }
        cout<<"time passing by"<<endl;
        sleep(1);
    
        flag=pthread_create(&tids[2],NULL,taxi_arrive,(void*)("Mike"));
        if(flag)
        {
            cout<<"pthread_create error:flag="<<flag<<endl;
            return flag;
        }
        cout<<"time passing by"<<endl;
        sleep(1);
    
        void *ans;
        for(int i=0; i<3; i++)
        {
            flag=pthread_join(tids[i],&ans);
            if(flag)
            {
                cout<<"pthread_join error:flag="<<flag<<endl;
                return flag;
            }
            cout<<"ans="<<ans<<endl;
        }
        return 0;
    }

    QQ20190715105012_thumb1

    分析:程序由一个条件变量,用于提示乘客有出租车到达,还有一个同步锁,乘客到达之后就是等车(条件变量),出租车到达之后就是通知乘客,我们看到乘客Susan到达之后,并没有乘坐先到的Jack的车,而是等到Mike的车到了之后再乘坐Mike的车,Jack的车白白的闲置了,为什么会造成这种原因呢?分析一下代码:我们发现Jack出租车到达之后调用pthread_cond_signal(&taxi_cond)发现没有乘客,然后就直接结束线程了。。。。

    正确的操作应该是:先到的Jack发现没有乘客,然后一直等待乘客,有乘客到了就直接走,而且我们应该统计一下乘客的数量

    做如下改进:

    1.增加乘客计数器,使得出租车在有乘客到达之后可以直接走,而不是又在原地等待别的乘客(僵死线程)

    2.出租车到达函数加个while循环,没有乘客的时候一直等待,直到乘客到来

    #include <iostream>
    #include<pthread.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<unistd.h>
    #include<errno.h>
    using namespace std;
    
    pthread_cond_t taxi_cond=PTHREAD_COND_INITIALIZER; //taix arrive cond
    pthread_mutex_t taxi_mutex=PTHREAD_MUTEX_INITIALIZER;// sync mutex
    
    
    void *traveler_arrive(void *name)
    {
        cout<<"Traveler:"<<(char*)name<<" needs a taxi now!"<<endl;
        pthread_mutex_lock(&taxi_mutex);
     
        pthread_cond_wait(&taxi_cond,&taxi_mutex);
        pthread_mutex_unlock(&taxi_mutex);
        cout<<"Traveler:"<<(char*)name<<" now got a taxi!"<<endl;
        pthread_exit((void*)0);
    }
    
    void *taxi_arrive(void *name)
    {
        cout<<"Taxi:"<<(char*)name<<" arriver."<<endl;
        
        pthread_exit((void*)0);
    }
    
    int main()
    {
        pthread_t tids[3];
        int flag;
    
        flag=pthread_create(&tids[0],NULL,taxi_arrive,(void*)("Jack"));
        if(flag)
        {
            cout<<"pthread_create error:flag="<<flag<<endl;
            return flag;
        }
        cout<<"time passing by"<<endl;
        sleep(1);
    
        flag=pthread_create(&tids[1],NULL,traveler_arrive,(void*)("Susan"));
        if(flag)
        {
            cout<<"pthread_create error:flag="<<flag<<endl;
            return flag;
        }
        cout<<"time passing by"<<endl;
        sleep(1);
    
        flag=pthread_create(&tids[2],NULL,taxi_arrive,(void*)("Mike"));
        if(flag)
        {
            cout<<"pthread_create error:flag="<<flag<<endl;
            return flag;
        }
        cout<<"time passing by"<<endl;
        sleep(1);
    
        void *ans;
        for(int i=0; i<3; i++)
        {
            flag=pthread_join(tids[i],&ans);
            if(flag)
            {
                cout<<"pthread_join error:flag="<<flag<<endl;
                return flag;
            }
            cout<<"ans="<<ans<<endl;
        }
        return 0;
    }

    三.读写锁

    可以多个线程同时读,但是不能多个线程同时写

    1.读写锁比互斥锁更加具有适用性和并行性

    2.读写锁最适用于对数据结构的读操作读操作次数多余写操作次数的场合!

    3.锁处于读模式时可以线程共享,而锁处于写模式时只能独占,所以读写锁又叫做共享-独占锁

    4.读写锁有两种策略:强读同步和强写同步

    在强读同步中,总是给读者更高的优先权,只要写者没有进行写操作,读者就可以获得访问权限

    在强写同步中,总是给写者更高的优先权,读者只能等到所有正在等待或者执行的写者完成后才能进行读

    不同的系统采用不同的策略,比如航班订票系统使用强写同步,图书馆查阅系统采用强读同步

    根据不同的业务场景,采用不同的策略

    1)初始化的销毁读写锁

    静态初始化:pthread_rwlock_t rwlock=PTHREAD_RWLOCK_INITIALIZER

    动态初始化:int pthread_rwlock_init(rwlock,NULL),NULL代表读写锁采用默认属性

    销毁读写锁:int pthread_rwlock_destory(rwlock)

    在释放某个读写锁的资源之前,需要先通过pthread_rwlock_destory函数对读写锁进行清理。释放由pthread_rwlock_init函数分配的资源

    如果你想要读写锁使用非默认属性,则attr不能为NULL,得给attr赋值

    int pthread_rwlockattr_init(attr),给attr初始化

    int pthread_rwlockattr_destory(attr),销毁attr

    2)以写的方式获取锁,以读的方式获取锁,释放读写锁

    int pthread_rwlock_rdlock(rwlock),以读的方式获取锁

    int pthread_rwlock_wrlock(rwlock),以写的方式获取锁

    int pthread_rwlock_unlock(rwlock),释放锁

    上面两个获取锁的方式都是阻塞的函数,也就是说获取不到锁的话,调用线程不是立即返回,而是阻塞执行,在需要进行写操作的时候,这种阻塞式获取锁的方式是非常不好的,你想一下,我需要进行写操作,不但没有获取到锁,我还一直在这里等待,大大拖累效率

    所以我们应该采用非阻塞的方式获取锁:

    int pthread_rwlock_tryrdlock(rwlock)

    int pthread_rwlock_trywrlock(rwlock)


    读写锁的样例:

    #include <iostream>
    #include<pthread.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<unistd.h>
    #include<errno.h>
    using namespace std;
    
    int num=5;
    pthread_rwlock_t rwlock;
    
    void *reader(void *arg)
    {
      pthread_rwlock_rdlock(&rwlock);
      cout<<"reader "<<(long)arg<<" got the lock"<<endl;
      pthread_rwlock_unlock(&rwlock);
      return 0;
    }
    
    void *writer(void *arg)
    {
      pthread_rwlock_wrlock(&rwlock);
      cout<<"writer "<<(long)arg<<" got the lock"<<endl;
      pthread_rwlock_unlock(&rwlock);
      return 0;
    }
    
    int main()
    {
      int flag;
      long n=1,m=1;
      pthread_t wid,rid;
      pthread_attr_t attr;
    
      flag=pthread_rwlock_init(&rwlock,NULL);
      if(flag)
      {
        cout<<"rwlock init error"<<endl;
        return flag;
      }
    
      pthread_attr_init(&attr);
      pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);//thread sepatate
    
      for(int i=0;i<num;i++)
      {
        if(i%3)
        {
          pthread_create(&rid,&attr,reader,(void *)n);
          cout<<"create reader "<<n<<endl;
          n++;
        }else
        {
          pthread_create(&wid,&attr,writer,(void *)m);
          cout<<"create writer "<<m<<endl;
          m++;
        }
      }
    
      sleep(5);//wait other done
      return 0;
    }

    QQ20190715152536_thumb2

    分析:3个读线程,2个写线程,读线程比写线程多

    当读写锁是写状态时,在锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞

    当读写锁是读状态时,在锁被解锁之前,所有视图以读模式对它进行加锁的线程都可以得到访问权,但是以写模式对它进行加锁的线程会被阻塞

    所以读写锁默认是强读模式!


    四.信号量

    信号量(sem)和互斥锁的区别:互斥锁只允许一个线程进入临界区,而信号量允许多个线程进入临界区

    1)信号量初始化

    int sem_init(&sem,pshared,v)

    pshared为0表示这个信号量是当前进程的局部信号量

    pshared为1表示这个信号量可以在多个进程之间共享

    v为信号量的初始值

    成功返回0,失败返回-1

    2)信号量值的加减

    int sem_wait(&sem):以原子操作的方式将信号量的值减去1

    int sem_post(&sem):以原子操作的方式将信号量的值加上1

    3)对信号量进行清理

    int sem_destory(&sem)


    通过信号量模拟2个窗口,10个客人进行服务的过程

    样例:

    #include <iostream>
    #include<pthread.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<unistd.h>
    #include<errno.h>
    #include<semaphore.h>
    using namespace std;
    
    
    int num=10;
    sem_t sem;
    
    void *get_service(void *cid)
    {
      int id=*((int*)cid);
      if(sem_wait(&sem)==0)
      {
         sleep(5);
         cout<<"customer "<<id<<" get the service"<<endl;
         cout<<"customer "<<id<<" done "<<endl;
         sem_post(&sem);
      }
      return 0;
    }
    
    int main()
    {
      sem_init(&sem,0,2);
      pthread_t customer[num];
      int flag;
    
      for(int i=0;i<num;i++)
      {
        int id=i;
        flag=pthread_create(&customer[i],NULL,get_service,&id);
        if(flag)
        {
          cout<<"pthread create error"<<endl;
          return flag;
        }else
        {
          cout<<"customer "<<i<<" arrived "<<endl;
        }
        sleep(1);
      }
    
      //wait all thread done
      for(int j=0;j<num;j++)
      {
        pthread_join(customer[j],NULL);
      }
      sem_destroy(&sem);
      return 0;
    }

    QQ20190715165657_thumb2

    分析:信号量的值代表空闲的服务窗口,每个窗口一次只能服务一个人,有空闲窗口,开始服务前,信号量-1,服务完成后信号量+1


    总结完毕:Linux c++线程同步的四种方式:互斥锁,条件变量,读写锁,信号量



    转载于:https://www.cnblogs.com/yinbiao/p/11190336.html

    展开全文
  • c++多线程同步——信号量。非常简单的MFC工程。
  • 一、进程线程概念 进程通常被定义为一个正在运行的程序的实例,是一个程序在其自身地址空间中的一次执行活动。 进程从来不执行任何东西,它是线程的容器。...二、C++线程创建函数 使用系统提供的API函数CreateT.
  • 线程结束后,传值出去, 给pthread_join()的参数2 */ sleep ( 3 ) ; struct sembuf buf1 [ ] = { { 0 , 1 , SEM_UNDO } } ; semop ( sem_mutex , buf1 , 1 ) ; debugPrint ( "semp_thread....
  • c++ 多线程向同一个文本写入信息 解决同步与互斥问题
  • C++多线程同步之Mutex(互斥量)

    千次阅读 2016-11-16 15:06:39
    一、互斥量Mutex同步多线程1、Win32平台相关函数和头文件#include HANDLE CreateMutex( LPSECURITY_ATTRIBUTESlpMutexAttributes, // 指向安全属性的指针 BOOLbInitialOwner, // 初始化互斥对象的所有者 ...
  • 原子操作,临界区(Critical section),通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。 内核模式下的方法有 1、互斥量:为协调共同对一个共享资源的单独访问而设计。 2、信号量·:为...
  • C++多线程并发(二)---线程同步之互斥锁

    万次阅读 多人点赞 2019-03-20 00:08:29
    在前一篇文章《C++多线程并发编程(一)—线程管理》中解释多线程并发时说到两个比较重要的概念: 多线程并发:在同一时间段内交替处理多个操作,线程切换时间片是很短的(一般为毫秒级),一个时间片多数时候...
  • C++实现线程同步的几种方式

    千次阅读 2020-08-24 14:38:01
    线程同步是指同一进程中的个线程互相协调工作从而达到一致性。之所以需要线程同步,是因为个线程同时对一个数据对象进行修改操作时,可能会对数据造成破坏,下面是个线程同时修改同一数据造成破坏的例子: ...
  • 包括TCP服务器客户端多线程通信同步程序(用select语句和WSAEVENT事件对象机制),基于Visual Studio2017实现发,已编译测试过,用到的朋友记得关闭SDL检查
  • 很不错的源码,3种多线程实现同步方法
  • 程序中通常将多线程同时访问的某个资源作为临界区,需要定义一个CRITICAL_SECTION类型的变量,然后调用InitializeCriticalSection函数对变量进行初始化; 函数声明: VOID InitializeCriticalSection(LPCRITICAL_...
  • 多线程间的状态同步,这个可用的机制很多,条件变量是广泛使用的一种。 今天我用一个简单的例子来给大家介绍下锁和条件变量的使用。 代码使用C++11 示例代码 #include #include #include #include std::mutex ...
  • C++多线程同步之事件(Event)

    千次阅读 2016-11-29 12:02:53
    线程同步Event,主要用于线程间的等待通知。 内核对象中,事件内核对象是个最基本的对象。 事件包含一个使用计数(与所有内核对象一样),一个用于指明该事件是个自动重置的事件还是人工重置的事件的布尔值,另一...
  • 10分钟,带你掌握C++多线程同步

    千次阅读 2018-08-10 11:29:22
    摘要:本文介绍了C++11中如何开启新线程,并详细讲解了线程的基础同步原语:mutex, lock_guard, unique_lock, condition variable和semaphore等。...本文以质数判定服务为例为大家分享C++多线程同步措施!   数十...
  • c++实现多线程同步

    千次阅读 2018-05-23 16:01:27
    线程同步是指同一进程中的个线程互相协调工作从而达到一致性。之所以需要线程同步,是因为个线程同时对一个数据对象进行修改操作时,可能会对数据造成破坏,下面是个线程同时修改同一数据造成破坏的例子: 1 #...
  • C++多线程--线程同步信号量

    千次阅读 2018-09-25 18:29:41
    1、信号量 信号量设置一个资源访问计数。当该计数值大于0的时候,该信号量对象为有信号状态,当该计数值等于0的时候,该信号量对象为无信号状态。 2、信号量的使用  (1)创建信号量  HANDLE CreateSemaphore...
  • C++实现多线程及其三种方法实现多线程同步

    万次阅读 多人点赞 2017-06-18 21:24:18
    1.调用windows API实现多线程 #include "stdafx.h" #include #include #include DWORD WINAPI myfun1(LPVOID lpParameter); //声明线程函数 DWORD WINAPI myfun2(LPVOID lpParameter); using namespace std; ...
  • 1.线程同步(unique_lock的使用) unique_lock中的unique表示独占所有权。 unique_lock独占的是mutex对象,就是对mutex锁的独占。 用法: (1)新建一个unique_lock 对象 (2)给对象传入一个std::mutex 对象作为...
  • C++多线程编程

    2018-11-28 22:09:29
    C++多线程编程,详细介绍C++多线程中的并发并行以及同步
  • c++多线程代码实例

    2018-09-27 13:27:28
    运用c++代码实现多线程同步互斥问题,文档内为最基础c++代码,新手也可看懂
  • C++多线程同步(采用互斥对象Mutex)
  • 原因是会阻塞主线程的消息循环,所以必须使用另一种 MsgWaitForMultipleObjects,即可以让消息通过,下面就是一个基于MsgWaitForMultipleObjects与Event,实现多线程同步这样。HANDLE g_Handle;//全局的句柄g_Handle...
  • C++线程同步

    千次阅读 2022-03-18 18:25:45
    文章目录一、线程同步题(1)原子操作(2)互斥锁(3)RAII包装类管理互斥锁 一、线程同步题 两个线程同时对一个全局变量++操作,保证最后的结果正确 (1)原子操作 #include <iostream> #include <thread&...
  • C++多线程--线程间通信与线程同步

    万次阅读 2016-10-27 17:11:42
    线程的同步 :虽然多线程能给我们带来好处,但是也有不少问题需要解决。例如,对于像磁盘驱动器这样独占性系统资源,由于线程可以执行进程的任何代码段,且线程的运行是由系统调度自动完成的,具有一定的不确定性,...
  • C++11 多线程同步

    千次阅读 2016-11-09 21:28:05
    出现数据竞争,一般会用临界区(Critical Section)、互斥量(Mutex)、信号量(Semaphore)、事件(Event)这四种方法来完成线程同步。 1、临界区 对于临界资源,多线程必须互斥地对它进行访问。每个...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 120,345
精华内容 48,138
关键字:

c++多线程同步

c++ 订阅