C/C++C/C++ 专题知识 移动 前端 Python Android Java

C++ 多线程

2021-04-08  本文已影响0人  zcwfeng

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

因为后面多线程知识,涉及更改变量和值的问题,所以前置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*作为形参的函数版本。

上一篇 下一篇

猜你喜欢

热点阅读