C++ 多线程

作者: zcwfeng | 来源:发表于2021-04-08 09:56 被阅读0次

    前置知识点回顾,值传递情况

    因为后面多线程知识,涉及更改变量和值的问题,所以前置demo来复习一下

    左值和右值

    ① 第一种情况【getInfo函数的info 与 main函数的result 是旧与新的两个变量而已,他们是值传递,所以右值修改时,影响不了里面的旧变量】
    ② 第二种情况【getInfo函数的info 与 main函数的result 是引用关系,一块内存空间 有多个别名而已,所以右值修改时,直接影响旧变量】

    #include <iostream>
    #include <vector>
    using namespace std;
    
    class Student {
    private:
        string info = "AAA";
    public:
        string getInfo(){
            return this->info;
        }
    public:
        string & getInfo_front(){
            return this->info;
        }
    };
    
    int main(){
        Student student;
        // 第一种情况
        student.getInfo() = "玉女心经";
        string result = student.getInfo();//左值
        cout << "①情况:" << result << endl;
        // 第一种情况
        result = student.getInfo_front() = "黯然销魂掌";//右值
        cout << "②情况:" << result << endl;
        return 0;
    }
    

    C++ 11 后出现的 自带 Thread

    (C++11 )thread 用的不多,我们最长用的是p_thread后面详细分析。简单看一下如何使用

    #include <iostream>
    #include <thread>
    #include <unistd.h>
    
    using namespace std;
    
    void runAction(int number){
        for (int i = 0; i < 10; ++i) {
            cout << "runAction:" << number << endl;
        }
    }
    int main(){
        //方式1 只等2秒 各种玩各种的,老死不相往来
        thread thread1(runAction,100);
        //我只等你2秒
        sleep(2);
    // 方式2, 等你执行完在执行
    //    thread thread2(runAction,100);
    //    thread2.join();
        cout << "main 弹栈" << endl;
    
        return 0;
    }
    

    runAction -> 相当于 Java的 run函数一样,异步线程 子线程

    ① 方式一 main只等2秒钟,各种玩各种的,老死不相往来
    thread1(runAction,100);
    ② 方式二 我等你执行完之后,我在执行
    thread2.join();

    pthread

    很多场景,包括Android,Linux编程,Java的底层,都是用到pthread 而不是C++ 11 之后加入的Thread。

    假设使用的是 Linux 操作系统,我们要使用 POSIX 编写多线程 C++ 程序。POSIX Threads 或 Pthreads 提供的 API 可在多种类 Unix POSIX 系统上可用,比如 FreeBSD、NetBSD、GNU/Linux、Mac OS X 和 Solaris。

    编译器默认支持情况

    ① windows 环境Mingw 没有 pthread
    ② windows 环境Vitual Studio 没有 pthread
    ③ Android NDK 有 pthread 默认
    ④ Linux pthread 默认
    ⑤ Clion 设置 Cygwin 有
    ⑥ Mac OS 默认有

    pthread 简单使用
    头文件pthread引入
    定义申请ID:pthread_t ID
    创建线程:pthread_create
    pthread_create 不同平台略有不同,核心参数一样

    macpro clion 环境
    
    int pthread_create(pthread_t * __restrict,
            const pthread_attr_t * _Nullable __restrict,
            void *(* _Nonnull)(void *), void * _Nullable __restrict);
    
    windows 环境,说明下参数
    
    int pthread_create (pthread_t *,  // 参数一:线程ID
                            const pthread_attr_t *, // 参数二:线程属性
                            void *(*)(void *), // 参数三:函数指针的规则
                            void *); // 参数四:给函数指针传递的内容,void * 可以传递任何内容
    

    看个例子

    #include <iostream>
    #include <pthread.h>
    using namespace std;
    //void * _Nullable (* _Nonnull)(void * _Nullable)
    void * customPthreadTask(void * pVoid){
        int number = *static_cast<int *>(pVoid);
        cout << "异步线程" << number << endl;
        return 0;
    }
    int number = 9527;
    int main(){
    
        pthread_t pthreadID;
        pthread_create(&pthreadID,0,customPthreadTask,&number);
        return 0;
    }
    

    customPthreadTask->异步线程 相当于Java的Thread.run函数一样
    C++转换static_cast 转换指针操作的
    int * number = static_cast<int >(pVoid);
    pVoid==number int的地址,所以我用int
    接收,很合理
    必须结果返回return 0;

    pthread 的三种情况

    ① 第一种情况,main函数只要结束,不等异步线程,全部结束
    ② 第二种情况,sleep 方式,我们开发者,千万不要让 main函数睡眠的方式,去等待异步线程
    ③ 第三种情况,main函数一直等待 异步线程,只有异步线程执行完成后,我在执行 join后面的代码

    
    #include <iostream>
    #include <pthread.h>
    #include <unistd.h>
    
    using namespace std;
    
    void * runTask(void * pVoid){
        int number = *static_cast<int *>(pVoid);
        cout << "异步线程" << number << endl;
        for (int i = 0; i < 10; ++i) {
            sleep(1);
            cout << "不要这么做,sleep:" << i << endl;
        }
        return 0;
    }
    int main(){
        int number = 9999;
        // main函数结束全部结束
        pthread_t pthreadID;
        pthread_create(&pthreadID,0,runTask,&number);
    // 演示第二种情况,正常开发不这么做
        sleep(3);
    
    // join情况
    //    pthread_join(pthreadID,0);
    
        cout <<"main 弹出栈"<<endl;
        return 0;
    }
    

    分离线程,非分离线程

    C++ 分离线程 和 非分离线程 区别,应用场景?

    分离线程: 各个线程都是自己运行自己的,老死不相往来,例如:main函数结束,全部结束,不会等待异步线程 【多线程情况下场景】

    非分离线程: 线程有协作的能力,例如:main函数线程会等待 异步线程执行完成后,我再执行 后面main函数的代码【协作,顺序执行 场景】

    C++ 互斥锁

    相当于,Java版本(synchronize) 多线程操作的安全 持有内置锁

    #include <iostream>
    #include <pthread.h>
    #include <unistd.h>// sleep
    #include <queue>
    
    using namespace std;
    // 定义一个全局的队列,用于 存储/获取
    queue<int> queueData;
    //互斥锁,不能有野指针
    pthread_mutex_t mute;
    
    void *task(void *pvoid) {
        /*synchronize(锁) {
            相当java code
        }*/
        // 锁住
        pthread_mutex_lock(&mute);
        cout << *static_cast<int *>(pvoid) << endl;
    
        if (!queueData.empty()) {
            cout << "异步数据,队列数据:" << queueData.front() << endl;
            queueData.pop();
        } else {
            cout << "异步线程,队列无数据" << endl;
        }
    
    
        // 解锁
        pthread_mutex_unlock(&mute);
    
        return 0;
    }
    
    int main() {
        // 初始化 互斥锁
        pthread_mutex_init(&mute, NULL);
        int i = 0;
        for (i = 1000; i < 1009; ++i) {
            queueData.push(i);
        }
        pthread_t pthreadIDArray[10];
        int j = 0;
        for (j = 0; j < 10; ++j) {
            pthread_create(&pthreadIDArray[j], 0, task, &j);
            // 不能使用 join,如果使用(就变成顺序的方式,就没有多线程的意义了,所以不能写join)
            // pthread_join(pthreadIDArray[i], 0);
        }
    
        // main函数等 异步线程
    //    sleep(12);
    
    
        // 销毁 互斥锁
        pthread_mutex_destroy(&mute);
        cout << "main函数即将弹栈..." << endl;
        // 每次运行 效果都不同:1,8,9,10,3,2,5,8
        // 每次运行 效果都是错乱
        return 0;
    }
    
    

    ① 通常线程存储队列用的比较多。pthread_mutex_t 声明锁
    ② pthread_mutex_lock 锁,pthread_mutex_unlock 解锁
    ③ pthread_mutex_init 初始化锁,pthread_mutex_destroy 销毁锁

    C++ 条件变量+互斥锁互斥锁

    相当于Java版本的(notify 与 wait 操作)

    自定义一个安全队列SafeQueue

    #ifndef TEMPC_SAFE_QUEUE_TOOL_H
    #define TEMPC_SAFE_QUEUE_TOOL_H
    
    
    #endif //TEMPC_SAFE_QUEUE_TOOL_H
    #pragma once
    
    #include <iostream>
    #include <pthread.h>
    #include <queue>
    
    using namespace std;
    
    // 定义模板
    template<typename T>
    
    class SafeQueue {
    private:
        queue<T> queue;//队列
        pthread_mutex_t mute;//互斥锁(不允许野指针)
        pthread_cond_t cond;//条件,等待或者唤醒(不允许野指针)
    public:
        SafeQueue() {
            pthread_mutex_init(&mute, 0);
            pthread_cond_init(&cond, 0);
        }
    
        ~ SafeQueue() {
            pthread_mutex_destroy(&mute);
            pthread_cond_destroy(&cond);
        }
    
        void add(T t) {
            pthread_mutex_lock(&mute);
            queue.push(t);
    
    //        pthread_cond_signal(&cond);//->java notify
            pthread_cond_broadcast(&cond);//->java notifyAll
            cout << "add queue.push notifyAll" << endl;
            pthread_mutex_unlock(&mute);
        }
    
        // 外面的人消费,可以直接消耗,也可以用引用
        void get(T &t) {
            pthread_mutex_lock(&mute);
            while (queue.empty()) {
                cout << "get empty 等待中" << endl;
                pthread_cond_wait(&cond, &mute);//->java wait
            }
            t = queue.front();
            queue.pop();
            pthread_mutex_unlock(&mute);
        }
    
    };
    

    注意引用传递, void get(T &t) ,利用引用,相当于赋值。模拟一个生产消费安全队列。

    // 条件变量+ 互斥锁 == java 的notify和wait
    #include <iostream>
    #include <pthread.h>
    #include "safe_queue_tool.h"
    using namespace std;
    SafeQueue<int> sq;
    
    void *getMethod(void *) {
        while (true) {
            cout << "getMethod" << endl;
            int value = 0;
            sq.get(value);
            cout << "消费get 得到数据:" << value << endl;
    
            if (-1 == value) {
                break;
            }
        }
    
        return 0;
    }
    
    void *setMethod(void *) {
        while (true) {
            cout << "setMethod" << endl;
            int value = 0;
            cout << "输入生产信息:" << endl;
            cin >> value;
    
            if (-1 == value) {
                sq.add(value);
                cout << "消费get全部执行完毕" << endl;
                break;
            }
            sq.add(value);
        }
        return 0;
    }
    
    int main() {
        pthread_t pthreadGet;
        pthread_create(&pthreadGet,0,getMethod,0);
        pthread_t pthreadSet;
        pthread_create(&pthreadSet,0,setMethod,0);
    
        pthread_join(pthreadSet,0);
        pthread_join(pthreadGet,0);
    
        return 0;
    }
    
    

    终止线程

    #include <pthread.h>
    pthread_exit (status) 
    

    在这里,pthread_exit 用于显式地退出一个线程。通常情况下,pthread_exit() 例程是在线程完成工作后无需继续存在时被调用。

    如果 main() 是在它所创建的线程之前结束,并通过 pthread_exit() 退出,那么其他线程将继续执行。否则,它们将在 main() 结束时自动被终止。

    #include <iostream>
    // 必须的头文件是
    #include <pthread.h>
    
    using namespace std;
    
    #define NUM_THREADS 5
    
    // 线程的运行函数
    void* say_hello(void* args)
    {
        cout << "Hello w3cschool!" << endl;
        return nullptr;
    }
    
    int main()
    {
        // 定义线程的 id 变量,多个变量使用数组
        pthread_t tids[NUM_THREADS];
        //for(auto & tid : tids)
        for(int i = 0; i < NUM_THREADS; ++i)
        {
            //参数依次是:创建的线程id,线程参数,调用的函数,传入的函数参数
            int ret = pthread_create(&tids[i], nullptr, say_hello, nullptr);
            if (ret != 0)
            {
                cout << "pthread_create error: error_code=" << ret << endl;
            }
        }
        //等各个线程退出后,进程才结束,否则进程强制结束了,线程可能还没反应过来;
        pthread_exit(nullptr);
    }
    

    再看一个例子:

    #include <iostream>
    #include <cstdlib>
    #include <pthread.h>
    #include <unistd.h>
    
    using namespace std;
    
    #define NUM_THREADS     5
    
    void *PrintHello(void *threadid)
    {
        // 对传入的参数进行强制类型转换,由无类型指针变为整形数指针,然后再读取
        int tid = *static_cast<int*>(threadid);
        cout << "Hello w3cschool!线程 ID, " << tid << endl;
    //    sleep(1);
    
        pthread_exit(nullptr);
    //    return nullptr;
    }
    
    int main ()
    {
        pthread_t threads[NUM_THREADS];
        int indexes[NUM_THREADS];// 用数组来保存i的值
        int rc;
        int i;
        for( i=0; i < NUM_THREADS; i++ ){
            cout << "main() : 创建线程, " << i << endl;
    
            indexes[i] = i; //先保存i的值
            // 传入的时候必须强制转换为void* 类型,即无类型指针
            rc = pthread_create(&threads[i], nullptr,
                                PrintHello, (void *)&(indexes[i]));
            if (rc){
                cout << "Error:无法创建线程," << rc << endl;
                exit(-1);
            }
        }
        pthread_exit(nullptr);
    }
    
    

    pthread 传递信息

    #include <iostream>
    #include <cstdlib>
    #include <pthread.h>
    
    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 = "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);
    }
    
    

    补充小点NULL 和 nullptr

    一、C程序中的NULL

    在C语言中,NULL通常被定义为:#define NULL ((void *)0)

    所以说NULL实际上是一个空指针,如果在C语言中写入以下代码,编译是没有问题的,因为在C语言中把空指针赋给int和char指针的时候,发生了隐式类型转换,把void指针转换成了相应类型的指针。

    int *pi = NULL;
    char *pc = NULL;

    二、C++程序中的NULL

    但是问题来了,以上代码如果使用C++编译器来编译则是会出错的,因为C++是强类型语言,void*是不能隐式转换成其他类型的指针的,所以实际上编译器提供的头文件做了相应的处理:

    #ifdef __cplusplus
    #define NULL 0
    #else
    #define NULL ((void *)0)
    #endif
    

    可见,在C++中,NULL实际上是0. 因为C++中不能把void*类型的指针隐式转换成其他类型的指针,所以为了解决空指针的表示问题,C++引入了0来表示空指针,这样就有了上述代码中的NULL宏定义。

    但是实际上,用NULL代替0表示空指针在函数重载时会出现问题,程序执行的结果会与我们的想法不同

    #include <iostream>
    using namespace std;
     
    void func(void* t)
    {
        cout << "func( void* )" << endl;
    }
     
    void func(int i)
    {
        cout << "func( int ) " << endl;
    }
     
     
    int main()
    {
        func(NULL);
        func(nullptr);
        system("pause");
        return 0;
    }
    
    func(NULL); 出现二义性,不同平台处理不同。func(0);
    

    在这段代码中,我们对函数func进行可重载,参数分别是void*类型和int类型,但是运行结果却与我们使用NULL的初衷是相违背的,因为我们本来是想用NULL来代替空指针,但是在将NULL输入到函数中时,它却选择了int形参这个函数版本,所以是有问题的,这就是用NULL代替空指针在C++程序中的二义性。

    三、C++中的nullptr

    为解决NULL代指空指针存在的二义性问题,在C++11版本(2011年发布)中特意引入了nullptr这一新的关键字来代指空指针,从上面的例子中我们可以看到,使用nullptr作为实参,确实选择了正确的以void*作为形参的函数版本。

    相关文章

      网友评论

        本文标题:C++ 多线程

        本文链接:https://www.haomeiwen.com/subject/ydsqkltx.html