C++的多线程

本文记录自己学习 C++多线程的过程和思考

C++的多线程?

  • C+11 中引入多线程支持,在C++11之前,我们必须在C中使用POSIX线程或线程库(pthread.h)
  • Std: : thread 是 C++中表示单个线程的线程类,启动步骤:1)创建线程对象;2)给线程调用对象传参;3)使用 start 启动一个新线程;4)设置线程阻塞方式
  • 注意:std:: thread 被初始化后,线程立即执行
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 1-使用匿名函数初始化线程
    auto f = [](params) {// Do Something};
    std::thread thread_object(f, params);
    // 2-使用类初始化线程
    class fn_object_class {
    // 重写了()操作符
    void operator()(params){// Do Something}
    int testFunc(params){// Do Something}
    }
    // 直接使用类的()操作符定义的函数创建线程对象
    std::thread thread_object(fn_object_class(), params)
    // 3-使用类的普通函数创建线程对象
    fn_object_class initClass;
    std::thread(&initClass::testFunc,params);

C++多线程给调用对象传参数的规则?

  • 当传入参数为基本数据类型( 整型(Integer)**,**字符型(Character)等) 时,会拷贝 一份给创建的线程
  • 当传入参数为指针时,会浅拷贝 一份给创建的线程,即拷贝对象的指针
  • 当传入的参数为引用时,实参必须用ref()函数处理 后传递给形参,否则编译不通过,此时不存在“拷贝”行为

C++多线程的join()阻塞方式?

  • 当前主线程等待设置线程结束后再继续运行,使用 joinable ()来判断 join ()可否调用
    1
    2
    3
    4
    5
    6
    7
    void threadFunc(){std::this_thread::sleep_for(std::chrono::seconds(1));}
    std::thread t1; // 声明线程
    t1.joinable() // false
    t1 = std::thread(threadFunc); // 实例化线程
    t1.joinable() // true
    t1.join(); //
    t1.joinable() // false

C++多线程的detach()阻塞方式?

  • 主线程不等待启动线程完成,直接往下执行。就必须保证线程结束之前可访问数据的有效性,使用指针和引用需要格外谨慎
    1
    2
    std::thread t1(callable);
    t1.detach();

C++多线程内置函数hardware_concurrency()?

  • 是一个 观察者函数,返回的是并发线程数
    1
    unsigned int con_threads= std::thread::hardware_concurrency(); // 4

C++多线程内置函数 get_id()?

  • 一个观察者函数,此函数返回std: 🧵 :id的值
    1
    2
    3
    4
    5
    void sleepThread(){this_thread::sleep_for(chrono::seconds(1));}
    thread thread1(sleepThread);
    thread thread2(sleepThread);
    thread::id t1_id = thread1.get_id(); // 139858743162624
    thread::id t2_id = thread2.get_id(); // 139858734769920

C++如何使用std: : future和std: :promise获取线程返回值?

  • std: :future,是一个类模板,它存储着一个未来的值, 这个变量可以通过std: :future提供的成员函数std: :future: :get()来得到, 如果在这个变量被赋值之前就有别的线程试图通过std: :future: :get()获取这个变量,那么这个线程将会被阻塞到这个变量可以获取为止
  • std: :promise同样也是一个类模板,它的对象承诺会在未来设置变量(这个变量也就是std: :future中的变量)。每一个std: :promise对象都有一个与之关联的std: :future对象。当std: :promise设置值的时候,这个值就会赋给std: :future中的对象了
  • 这两个类在获取程序返回值的时候需要配合使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    void initiazer(std::promise &promiseObj){
    cout << "Inside thread: " << std::this_thread::get_id() << endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    promiseObj.set_value(35);
    }
    int main(){
    std::promise promiseObj;
    std::future futureObj = promiseObj.get_future();
    std::thread th(initiazer, std::ref(promiseObj));
    //promiseObj.set_value(35)运行前,使用futureObj.get(),主线程将阻塞
    std::cout << futureObj.get() << std::endl;
    th.join();
    return 0;
    }

C++上Win32API的临界区 (CriticalSection)使用?

  • 每个线程中访问临界资源的那段程序称为临界区(Critical Section)。可以保证在任意时刻只允许一个线程对共享资源进行访问,如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开
  • 操作原语
    1
    2
    1. EnterCriticalSection ():进入临界区,后续必须匹配 LeaveCriticalSection, 否则资源不会被释放;
    2. LeaveCriticalSection (): 释放临界区
  • 虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    #include<process.h>
    #include<windows.h>
    #include<stdio.h>
    //100张票
    int tickets = 100;
    //1. 临界区结构
    CRITICAL_SECTION Section;
    void __cdecl SellThread1(void* param)
    {
    char* name = (char*)param;
    while (tickets > 0)
    {
    //3.进入临界区,禁止其他线程访问
    EnterCriticalSection(&Section);
    if (tickets > 0)
    {
    Sleep(10);
    //CPU恰好执行到这里,这个时候线程时间片到了,并且此时还剩最后一张票
    printf("%s卖出第%d张票!\n", name, tickets--);
    }
    //4.离开临界区
    LeaveCriticalSection(&Section);
    }
    }
    void __cdecl SellThread2(void* param)
    {
    char* name = (char*)param;
    while (tickets > 0)
    {
    //尝试进入临界区,不会阻塞线程
    if (TryEnterCriticalSection(&Section))
    {
    if (tickets > 0)
    {
    Sleep(10);
    //CPU恰好执行到这里,线程时间片到了,并且此时还剩最后一张票
    printf("%s卖出第%d张票!\n", name, tickets--);
    }
    //离开临界区
    LeaveCriticalSection(&Section);
    // TryEnterCriticalSection速度很快,如果没有这个,除第一个是"售窗A",其他都是"售窗B"
    Sleep(2);
    }
    }
    }
    int main()
    {
    //2.初始化临界区
    InitializeCriticalSection(&Section);
    printf("开始卖票了!\n");
    //创建两个售票窗口
    uintptr_t t1 = _beginthread(SellThread1, 0, "售窗A");
    uintptr_t t2 = _beginthread(SellThread2, 0, "售窗B");
    //无限等待两个线程全部执行完毕
    HANDLE hArr[] = { (HANDLE)t1, (HANDLE)t2 };
    WaitForMultipleObjects(2, hArr, true, INFINITE);
    printf("卖票结束!\n");
    //5.删除临界区资源
    DeleteCriticalSection(&Section);
    return 0;
    }

C++上Win32API的互斥锁(Mutex)使用?

  • 互斥量跟临界区很相似,只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问资源
  • 操作原语
    1
    2
    3
    4
    CreateMutex()创建一个互斥量
    OpenMutex()打开一个互斥量
    ReleaseMutex()释放互斥量
    WaitForMultipleObjects() 等待互斥量对象
  • 互斥量比临界区复杂。因为使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    #include<process.h>
    #include<windows.h>
    #include<stdio.h>
    void __cdecl SellThread1(void* param);
    void __cdecl SellThread2(void* param);
    //10张票
    int tickets = 10;
    HANDLE hMutex = INVALID_HANDLE_VALUE;
    int main()
    {
    //创建互斥体,此刻为有信号状态
    hMutex = CreateMutex(NULL, FALSE, L"售票互斥体");
    printf("开始卖票了!\n");
    //创建两个售票窗口
    uintptr_t t1 = _beginthread(SellThread1, 0, "售口窗口A");
    uintptr_t t2 = _beginthread(SellThread2, 0, "售口窗口B");
    //无限等待两个线程全部执行完毕
    HANDLE hArr[] = { (HANDLE)t1, (HANDLE)t2 };
    WaitForMultipleObjects(2, hArr, true, INFINITE);
    printf("卖票结束!\n");
    return 0;
    }
    void __cdecl SellThread1(void* param)
    {
    char* name = (char*)param;
    while (tickets > 0)
    {
    //如果这个互斥体为有信号状态(没有线程拥有它),则线程获取它后继续执行
    WaitForSingleObject(hMutex, INFINITE);
    if (tickets > 0)
    {
    Sleep(10);
    printf("%s卖出第%d张票!\n", name, tickets--);
    }
    //释放对互斥体的拥有权,它变成有信号状态
    ReleaseMutex(hMutex);
    }
    }
    void __cdecl SellThread2(void* param)
    {
    char* name = (char*)param;
    while (tickets > 0)
    {
    //如果这个互斥体为有信号状态(没有线程拥有它),则线程获取它后继续执行
    WaitForSingleObject(hMutex, INFINITE);
    if (tickets > 0)
    {
    Sleep(10);
    printf("%s卖出第%d张票!\n", name, tickets--);
    }
    //释放对互斥体的拥有权,它变成有信号状态
    ReleaseMutex(hMutex);
    }
    }

C++上Win32API的信号量(Semaphore)使用?

  • 信号量对象对线程的同步方式与前面几种方法不同,信号允许多个线程同时使用共享资源,这与操作系统中的 PV 操作相同。它指出了同时访问共享资源的线程最大数目。它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目
  • 操作原语
    1
    2
    3
    4
    CreateSemaphore()创建信号量 时即要同时指出允许的最大资源计数和当前可用资源计数
    OpenSemaphore()打开一个信号量
    ReleaseSemaphore()函数将当前可 用资源计数加1
    WaitForSingleObject() 等待信号量
    • 一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目, 不能在允许其他线程的进入,此时的信号量信号将无法发出
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    #include<windows.h>
    #include<stdio.h>
    DWORD WINAPI ThreadFun(LPVOID paramter);
    //车辆名称和停车时间
    struct Car
    {
    char name[20];
    DWORD time;
    };
    HANDLE hSemaphore = INVALID_HANDLE_VALUE;
    int main()
    {
    //只有3个停车位资源
    hSemaphore = CreateSemaphore(NULL, 3, 3, L"停车位");
    HANDLE hArr[5] = { INVALID_HANDLE_VALUE };
    for (int i = 0; i < 5; ++i)
    {
    Car* pCar = new Car;
    sprintf(pCar->name, "车辆%c", 'A' + i);
    pCar->time = 3 + i * 3;
    //创建车辆线程
    hArr[i] = CreateThread(NULL, 0, ThreadFun, (LPVOID)pCar, 0, NULL);
    }

    //等待所有线程执行完毕
    WaitForMultipleObjects(5, hArr, true, INFINITE);
    return 0;
    }
    DWORD WINAPI ThreadFun(LPVOID paramter)
    {
    //如果有剩余停车位资源(有信号状态),就放行
    WaitForSingleObject(hSemaphore, INFINITE);
    Car* pCar = (Car*)paramter;
    printf("%s进入停车场,停车%d秒!\n", pCar->name, pCar->time);
    Sleep(pCar->time * 1000);
    printf("%s驶离停车场!\n", pCar->name);
    //释放一个停车位(信号量+1)
    ReleaseSemaphore(hSemaphore, 1, NULL);
    return 0;
    }

C++上Win32API的事件 (Event)使用?

  • 事件对象也可以通过通知操作的方式来保持线程的同步。并且可以实现不同进程中的线程同步操作
  • 操作原语
    1
    2
    3
    4
    5
    CreateEvent()创建一个信号量
    OpenEvent()打开一个事件
    SetEvent()回置事件
    WaitForSingleObject() 等待一个事件
    WaitForMultipleObjects()等待多个事件
  • 事件(Event)是 WIN32提供的最灵活的线程可同步方式,一个事件有两种状态:激发状态和未激发状态。也称有信号状态和无信号状态。事件又分两种类型:手动重置事件和自动重置事件。手动重置事件被设置为激发状态后,会唤醒所有等待的线程,而且一直保持为激发状态,直到程序重新把它设置为未激发状态。自动重置事件被设置为激发状态后,会唤醒“一个”等待中的线程,然后自动恢复为未激发状态。所以用自动重置事件来同步两个线程比较理想
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    #include<process.h>
    #include<windows.h>
    #include<stdio.h>
    void __cdecl SellThread1(void* param);
    void __cdecl SellThread2(void* param);
    //10张票
    int tickets = 10;
    HANDLE hEvent = INVALID_HANDLE_VALUE;
    int main()
    {
    //创建事件,此刻为有信号状态
    //自动重置信号状态, 初始化为有信号状态,线程可以直接获取
    hEvent = CreateEvent(NULL, FALSE, TRUE, L"事件对象");
    Sleep(1000);
    //主线程休眠3秒之后,将信号量设置为无信号状态
    //ResetEvent(hEvent);
    printf("开始卖票了!\n");
    //创建两个售票窗口
    uintptr_t t1 = _beginthread(SellThread1, 0, "售口窗口A");
    uintptr_t t2 = _beginthread(SellThread2, 0, "售口窗口B");
    //无限等待两个线程全部执行完毕
    HANDLE hArr[] = { (HANDLE)t1, (HANDLE)t2 };
    WaitForMultipleObjects(2, hArr, true, INFINITE);
    printf("卖票结束!\n");
    return 0;
    }
    void __cdecl SellThread1(void* param)
    {
    char* name = (char*)param;
    while (tickets > 0)
    {
    //如果事件对象为有信号状态(没有线程拥有它),则线程可以获取它后继续执行
    //自动重置的事件对象,调用了WaitForSingleObject函数之后,自动重置为无信号
    WaitForSingleObject(hEvent, INFINITE);
    if (tickets > 0)
    {
    Sleep(10);
    //CPU恰好执行到这里,这个时候线程时间片到了,并且此时还剩最后一张票
    printf("%s卖出第%d张票!\n", name, tickets--);
    }
    //SetEvent让事件对象变成有信号状态
    SetEvent(hEvent);
    }
    }
    void __cdecl SellThread2(void* param)
    {
    char* name = (char*)param;
    while (tickets > 0)
    {
    //如果事件对象为有信号状态(没有线程拥有它),则线程可以获取它后继续执行
    //自动重置的事件对象,调用了WaitForSingleObject函数之后,自动重置为无信号
    WaitForSingleObject(hEvent, INFINITE);
    if (tickets > 0)
    {
    Sleep(10);
    //CPU恰好执行到这里,这个时候线程时间片到了,并且此时还剩最后一张票
    printf("%s卖出第%d张票!\n", name, tickets--);
    }
    //SetEvent让事件对象变成有信号状态
    SetEvent(hEvent);
    }
    }

C++上Win32API的多线程实例-生产者消费者问题(Producer-consumer problem)?

  • 该问题描述了两个线程(“生产者”和“消费者”)使用块缓冲区。生产者生成数据放到缓冲区中,消费者在缓冲区取出数据。问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区空时取出数据
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    using namespace std;
    //缓冲区存储的数据类型
    struct CacheData
    {
    //商品id
    int id;
    //商品属性
    string data;
    };
    queue Q;
    //缓冲区最大空间
    const int MAX_CACHEDATA_LENGTH = 10;
    //互斥量,生产者之间,消费者之间,生产者和消费者之间,同时都只能一个线程访问缓冲区
    mutex m;
    condition_variable condConsumer;
    condition_variable condProducer;
    //全局商品id
    int ID = 1;
    //消费者动作
    void ConsumerActor()
    {
    unique_lock lockerConsumer(m);
    cout << "[" << this_thread::get_id() << "] 获取了锁" << endl;
    while (Q.empty())
    {
    cout << "因为队列为空,所以消费者Sleep" << endl;
    cout << "[" << this_thread::get_id() << "] 不再持有锁" << endl;
    //队列空, 消费者停止,等待生产者唤醒
    condConsumer.wait(lockerConsumer);
    cout << "[" << this_thread::get_id() << "] Weak, 重新获取了锁" << endl;
    }
    cout << "[" << this_thread::get_id() << "] ";
    CacheData temp = Q.front();
    cout << "- ID:" << temp.id << " Data:" << temp.data << endl;
    Q.pop();
    condProducer.notify_one();
    cout << "[" << this_thread::get_id() << "] 释放了锁" << endl;
    }
    //生产者动作
    void ProducerActor()
    {
    unique_lock lockerProducer(m);
    cout << "[" << this_thread::get_id() << "] 获取了锁" << endl;
    while (Q.size() > MAX_CACHEDATA_LENGTH)
    {
    cout << "因为队列为满,所以生产者Sleep" << endl;
    cout << "[" << this_thread::get_id() << "] 不再持有锁" << endl;
    //队列满,生产者停止,等待消费者唤醒
    condProducer.wait(lockerProducer);
    cout << "[" << this_thread::get_id() << "] Weak, 重新获取了锁" << endl;
    }
    cout << "[" << this_thread::get_id() << "] ";
    CacheData temp;
    temp.id = ID++;
    temp.data = "*****";
    cout << "+ ID:" << temp.id << " Data:" << temp.data << endl;
    Q.push(temp);
    condConsumer.notify_one();
    cout << "[" << this_thread::get_id() << "] 释放了锁" << endl;
    }
    //消费者
    void ConsumerTask()
    {
    while(1)
    {
    ConsumerActor();
    }
    }
    //生产者
    void ProducerTask()
    {
    while(1)
    {
    ProducerActor();
    }
    }
    //管理线程的函数
    void Dispatch(int ConsumerNum, int ProducerNum)
    {
    vector thsC;
    for (int i = 0; i < ConsumerNum; ++i)
    {
    thsC.push_back(thread(ConsumerTask));
    }
    vector thsP;
    for (int j = 0; j < ProducerNum; ++j)
    {
    thsP.push_back(thread(ProducerTask));
    }
    for (int i = 0; i < ConsumerNum; ++i)
    {
    if (thsC[i].joinable())
    {
    thsC[i].join();
    }
    }
    for (int j = 0; j < ProducerNum; ++j)
    {
    if (thsP[j].joinable())
    {
    thsP[j].join();
    }
    }
    }
    int main()
    {
    //一个消费者线程,5个生产者线程,则生产者经常要等待消费者
    Dispatch(1,5);
    return 0;
    }

C++上 Win 32 API 自带的多线程同步与互斥的临界区、锁、信号量和事件的区别?

  • 互斥量与临界区的作用非常相似,但互斥量是可以命名的,也就是说它可以跨越进程使用。所以创建互斥量需要的资源更多,所以如果只为了在进程内部是用的话使用临界区会带来速度上的优势并能够减少资源占用量。因为互斥量是跨进程的互斥量一旦被创建,就可以通过名字打开它
  • 互斥量,信号灯,事件都可以被跨越进程使用来进行同步数据操作,而其他的对象与数据同步操作无关

C++上STD库的互斥锁 (Mutex)使用?

  • C++ 11中声明的互斥量,包括:1)std::mutex,独占的互斥量,不能递归使用;2)std::time_mutex,带超时的独占互斥量,不能递归使用;3)std::recursive_mutex,递归互斥量,不带超时功能;4)std::recursive_timed_mutex,带超时的递归互斥量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <mutex>
#include <iostream>
#include <thread>
volatile int counter(0); // non-atomic counter
std::mutex mtx;
void increases()
{
for (int i = 0; i < 100; i++)
{
mtx.lock();
++counter;
mtx.unlock();
}
}
int main(int argc, char** argv)
{
std::thread threads[10];
for (int i = 0; i < 10; i++)
{
threads[i] = std::thread(increases);
}
for (auto& th : threads) th.join();
std::cout << " successful increases of the counter " << counter << std::endl;
return 0;
}

C++上STD库的临界区 (CriticalSection)使用?

  • C++上使用互斥锁实现临界区 (CriticalSection),代码使用std::lock_guard实现,这是因为防止使用mutex加锁解锁的时候,忘记解锁unlock了
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    #include <future>
    #include <thread>
    #include <chrono>
    #include <iostream>
    #include <mutex>
    int g_id = 0;
    //在构造函数初始化临界区,在析构函数重置临界区
    std::mutex mutex_g_id;
    int consumer(void)
    {
    //其他线程想得到mutex_g_id就会被挂起,除非当前lock释放了mutex_g_id
    //lock的构造函数会调用lock,独占mutex,析构函数调用unlock释放独占
    std::lock_guard<std::mutex> lock(mutex_g_id);
    g_id = 0;
    for (size_t i = 1; i <= 100; i++)
    {
    g_id += i;
    //当前线程休息,放弃CPU,啥也不干,等待操作系统线程调度算法再调度醒
    //其他线程正好去擅自改写g_id
    std::this_thread::sleep_for(std::chrono::duration<int, std::milli>(5));
    }
    return g_id;
    }
    int producer(void)
    {
    std::lock_guard<std::mutex> lock(mutex_g_id);
    g_id = 0;
    for (size_t i = 1; i <= 100; i++)
    {
    g_id += i;
    std::this_thread::sleep_for(std::chrono::duration<int, std::milli>(5));
    }
    return g_id;
    }
    int main(void)
    {
    auto a = std::async(std::launch::async, consumer);
    auto b = std::async(std::launch::async, producer);
    std::cout << "g_id = (1+100)*100/2 = 101*50 = 5050 = " << a.get() << std::endl;;
    std::cout << "g_id = (1+100)*100/2 = 101*50 = 5050 = " << b.get() << std::endl;;
    return 0;
    }

C++上STD库的信号量 (Semaphore)使用?

  • 信号量直到 C20 才被支持, C11 通过互斥锁和条件变量实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    #include <iostream>
    #include <semaphore>
    #include <thread>
    using namespace std;
    std::counting_semaphore<3> csem(0);
    binary_semaphore bsem(0);
    // semaphore release = condition_variable notify
    // semaphore acquire = condition_variable wait
    void task()
    {
    cout << "task:ready to recv signal \n";
    csem.acquire();
    cout << "task:acquire end\n";
    }
    int main()
    {
    thread t0(task);
    thread t1(task);
    thread t2(task);
    thread t3(task);
    thread t4(task);
    cout << "main:ready to signal :release\n";
    csem.release(3);
    cout << "main: signal end\n";
    t0.join();
    t1.join();
    t2.join();
    t3.join();
    t4.join();
    }

C上,Win32API的多线程同步和C STD库自带的多线程同步的区别?

  • Win32API:使用类似EnterCriticalSection、CreateMutex、CreateSemaphore、CreateEvent创建临界区、互斥锁、信号量、事件
  • STD库:使用std::mutex、std::创建互斥锁、信号量、事件
  • STD 库的实现方式比 Win32API 的实现方式更快

C线程库(pthread.h)的使用?

  • 创建线程并终止线程
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    using namespace std;
    #define NUM_THREADS 5
    // 线程的运行函数
    void* say_hello(void* args)
    {
    cout << "Hello Runoob!" << endl;
    return 0;
    }
    int main()
    {
    // 定义线程的 id 变量,多个变量使用数组
    pthread_t tids[NUM_THREADS];
    for(int i = 0; i < NUM_THREADS; ++i)
    {
    //参数依次是:创建的线程id,线程参数,调用的函数,传入的函数参数
    int ret = pthread_create(&tids[i], NULL, say_hello, NULL);
    if (ret != 0)
    {
    cout << "pthread_create error: error_code=" << ret << endl;
    }
    }
    //等各个线程退出后,进程才结束,否则进程强制结束了,线程可能还没反应过来;
    pthread_exit(NULL);
    }
  • 向线程传递参数:通过结构传递多个参数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    using namespace std;
    #define NUM_THREADS 5
    struct thread_data{
    int thread_id;
    char *message;
    };
    void *PrintHello(void *threadarg)
    {
    struct thread_data *my_data;
    my_data = (struct thread_data *) threadarg;
    cout << "Thread ID : " << my_data->thread_id ;
    cout << " Message : " << my_data->message << endl;
    pthread_exit(NULL);
    }
    int main ()
    {
    pthread_t threads[NUM_THREADS];
    struct thread_data td[NUM_THREADS];
    int rc;
    int i;
    for( i=0; i < NUM_THREADS; i++ ){
    cout <<"main() : creating thread, " << i << endl;
    td[i].thread_id = i;
    td[i].message = (char*)"This is message";
    rc = pthread_create(&threads[i], NULL,
    PrintHello, (void *)&td[i]);
    if (rc){
    cout << "Error:unable to create thread," << rc << endl;
    exit(-1);
    }
    }
    pthread_exit(NULL);
    }
  • 连接和分离线程
    • 当创建一个线程时,它的某个属性会定义它是否是可连接的(joinable)或可分离的(detached)。只有创建时定义为可连接的线程才可以被连接。如果线程创建时被定义为可分离的,则它永远也不能被连接
    • pthread_join() 子程序阻碍调用程序,直到指定的 threadid 线程终止为止
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      #include 
      #include
      #include
      #include
      using namespace std;
      #define NUM_THREADS 5
      void *wait(void *t)
      {
      int i;
      long tid;
      tid = (long)t;
      sleep(1);
      cout << "Sleeping in thread " << endl;
      cout << "Thread with id : " << tid << " ...exiting " << endl;
      pthread_exit(NULL);
      }
      int main ()
      {
      int rc;
      int i;
      pthread_t threads[NUM_THREADS];
      pthread_attr_t attr;
      void *status;
      // 初始化并设置线程为可连接的(joinable)
      pthread_attr_init(&attr);
      pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
      for( i=0; i < NUM_THREADS; i++ ){
      cout << "main() : creating thread, " << i << endl;
      rc = pthread_create(&threads[i], NULL, wait, (void *)&i );
      if (rc){
      cout << "Error:unable to create thread," << rc << endl;
      exit(-1);
      }
      }
      // 删除属性,并等待其他线程
      pthread_attr_destroy(&attr);
      for( i=0; i < NUM_THREADS; i++ ){
      rc = pthread_join(threads[i], &status);
      if (rc){
      cout << "Error:unable to join," << rc << endl;
      exit(-1);
      }
      cout << "Main: completed thread id :" << i ;
      cout << " exiting with status :" << status << endl;
      }
      cout << "Main: program exiting." << endl;
      pthread_exit(NULL);
      }

C++中的pthread和thread的区别?

  • pthread早于thread出现,本来是在类POSIX系统中用来多线程编程的,Windows原生不支持
  • C11之后, 只要Windows、Linux支持C11都可以使用原生的thread头文件
  • thread是**C++**的API, 不可以在C++中调用,换句话说,它更加简单和安全
  • pthread是一个C的API,因此它不提供任何RAII,这使得它更难使用,更容易出错,特别是就异常安全性

参考:

  1. https://zhuanlan.zhihu.com/p/360957905
  2. https://zhuanlan.zhihu.com/p/385838959
  3. https://blog.csdn.net/u014779536/article/details/116330524
  4. C++多线程编程:多线程同步之临界区 CriticalSection_criticalsection多线程_超级大洋葱806的博客-CSDN博客
  5. C++多线程编程:多线程同步之线程死锁_超级大洋葱806的博客-CSDN博客
  6. C多线程编程:同步之信号量 Semaphore_c semaphore_超级大洋葱806的博客-CSDN博客
  7. C++多线程编程:同步之互斥量Mutex_超级大洋葱806的博客-CSDN博客
  8. C多线程编程:同步之事件Event_c事件同步_超级大洋葱806的博客-CSDN博客
  9. C多线程编程:同步之PV操作_c pv操作_超级大洋葱806的博客-CSDN博客
  10. https://zhuanlan.zhihu.com/p/598993031
  11. http://www.sevangelatos.com/a-windows-mutex-is-not-a-mutex/
  12. https://zhuanlan.zhihu.com/p/111733286
  13. https://blog.csdn.net/junxuezheng/article/details/128650064
  14. C++ 多线程学习笔记(2):线程启动、结束、创建线程方法_c++线程_云端FFF的博客-CSDN博客