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