c++多线程学习
学习资料来源于C++多线程菜鸟教程:c++多线程
本人使用环境 manjaro ide为vscode c++11,
这里需要注意,对于线程相关代码编译时需加入 -lpthread 即 g++ a.cpp -lpthread
否则会显示undefined reference to `pthread_create'
我这里使用为vscode按照刚开始配置文件无法通过vsc直接编译,但是可以修改tasks.json文件
在args里面加入 "-lpthread"即可 以下给我我的配置文件 修改后可直接编译运行
{
"version": "2.0.0",
"tasks": [
{
"label": "Compile", // 任务名称,与launch.json的preLaunchTask相对应
"command": "clang++", // 要使用的编译器
"args": [
"${file}",
"-o", // 指定输出文件名,不加该参数则默认输出a.exe,Linux下默认a.out
"${fileDirname}/${fileBasenameNoExtension}.exe",
"-g", // 生成和调试有关的信息
"-Wall", // 开启额外警告
"-static-libgcc", // 静态链接
"-lpthread",
"-fcolor-diagnostics", // 彩色的错误信息?但貌似clang默认开启而gcc不接受此参数
"-std=c++11" // C语言最新标准为c11,或根据自己的需要进行修改
], // 编译命令参数
"type": "shell", // 可以为shell或process,前者相当于先打开shell再输入命令,后者是直接运行命令
"group": {
"kind": "build",
"isDefault": true // 设为false可做到一个tasks.json配置多个编译指令,需要自己修改本文件,我这里不多提
},
"presentation": {
"echo": true,
"reveal": "always", // 在“终端”中显示编译信息的策略,可以为always,silent,never。具体参见VSC的文档
"focus": false, // 设为true后可以使执行task时焦点聚集在终端,但对编译c和c++来说,设为true没有意义
"panel": "shared" // 不同的文件的编译信息共享一个终端面板
}
// "problemMatcher":"$gcc" // 如果你不使用clang,去掉前面的注释符,并在上一条之后加个逗号。照着我的教程做的不需要改(也可以把这行删去)
}
]
}
接下开始学习多线程:
首先是线程的创建
pthread_create (thread, attr, start_routine, arg)
该函数有4个参数,分别代表
thread | 指向线程标识符指针。 |
attr | 一个不透明的属性对象,可以被用来设置线程属性。您可以指定线程属性对象,也可以使用默认值 NULL。 |
start_routine | 线程运行函数起始地址,一旦线程被创建就会执行。 |
arg | 运行函数的参数。它必须通过把引用作为指针强制转换为 void 类型进行传递。如果没有传递参数,则使用 NULL。 |
看起来可能不是很清楚,直接上代码。
#include <iostream>
#include<bits/stdc++.h>
using namespace std;
const int maxn=5
void *PrintHello(void *threadid)
{
int tid = *((int*)threadid);// 对传入的参数进行强制类型转换,由无类型指针变为整形数指针,然后再读取
cout << "Hello Runoob! 线程 ID, " << tid << endl;
pthread_exit(NULL);
}
int main ()
{
pthread_t threads[maxn];
int indexes[maxn];// 用数组来保存i的值
for(int i=0; i < NUM_THREADS; i++ )
{
cout << "main() : 创建线程, " << i << endl;
indexes[i] = i; //先保存i的值
// 传入的时候必须强制转换为void* 类型,即无类型指针
int rc = pthread_create(&threads[i], NULL,PrintHello,(void *)&(indexes[i]));
if (rc)
{
cout << "Error:无法创建线程," << rc << endl;
exit(-1);
}
}
pthread_exit(NULL);
}
这里有个问题,如果pringhello函数里没有pthread_exit()函数的话,程序会运行到一半正常终止,目前我也不知道是什么原因,猜测可能是线程没有运行完结果进程直接结束了,导致后续线程无法运行。
可以看到4个参数中,第3个参数代表该线程需要完成的任务,而最后一个参数是将在线程开始函数的传入的参数,那么有个问题,如何给函数传入多个参数呢,
#include <bits/stdc++.h>
using namespace std;
const int maxn=5;
struct thread_data//创建一个结构体 格式为将传入的参数
{
int thread_id;
char *message;
};
void *print(void *threadarg)//函数这里传入的仍未空指针
{
thread_data *mydata;
mydata=(thread_data*) threadarg;//通过强制类型转换将参数列表提取出来
cout<<"Thread ID: "<<mydata->thread_id<<endl;
cout<<"Thread message: "<<mydata->message<<endl;
pthread_exit(NULL);
}
int main()
{
pthread_t tids[maxn];
thread_data td[maxn];//输入参数
for(int i=0;i<5;i++)
{
cout<<"main() : create thread, "<<i<<endl;
td[i].thread_id=i;
td[i].message="makise kurisu";
int rc=pthread_create(&tids[i],NULL,print,(void*)&td[i]);//参数强转为void*
if(rc)
{
cout<<"error"<<endl;
exit(-1);
}
}
pthread_exit(NULL);
}
可以看到 通过类型强转实现了函数传入多个参数的方法,目前我只学习了这一个写法,不清楚有没有其他的,日后在更,现在的目的只是单纯的了解多线程的基础操作和原理。
线程的属性由结构体pthread_attr_t管理,
typedef struct
{
int detachstate; 线程的分离状态
int schedpolicy; 线程调度策略
struct sched_param schedparam; 线程的调度参数
int inheritsched; 线程的继承性
int scope; 线程的作用域
size_t guardsize; 线程栈末尾的警戒缓冲区大小
int stackaddr_set; void * stackaddr; 线程栈的位置
size_t stacksize; 线程栈的大小
}pthread_attr_t;
使用join来连接线程
#include <iostream>
#include <cstdlib>
#include <pthread.h>
#include <unistd.h>
using namespace std;
#define NUM_THREADS 5
void *wait(void *t)
{
int tid= *((int*)t);
sleep(1);
cout << "Sleeping in thread " << endl;
int status = 10 + *(( int * )t); //线程退出时添加退出的信息,status供主程序提取该线程的结束信息
pthread_exit( ( void* )status );
}
int main ()
{
int arr[NUM_THREADS];
pthread_t threads[NUM_THREADS];
pthread_attr_t attr;
void *status;
// 初始化并设置线程为可连接的(joinable)
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
//该行表示将该参数设置为可连接的,即主程序进程会等线程运行完再去执行 实现同步
for(int i=0; i < NUM_THREADS; i++ )
{
cout << "main() : creating thread, " << i << endl;
arr[i]=i;
int rc = pthread_create(&threads[i], NULL, wait, (void *)&arr[i] );
if (rc)
{
cout << "Error:unable to create thread," << rc << endl;
exit(-1);
}
}
// 删除属性,并等待其他线程
pthread_attr_destroy(&attr);
for( int i = 0; i < NUM_THREADS; ++i )
{
int rc = pthread_join( threads[i], &status ); //主程序join每个线程后取得每个线程的退出信息status
if(rc) cout << "pthread_join error:error_code=" << rc << endl;
else cout << "pthread_join get status:" << (long)status << endl;
}
cout << "Main: program exiting." << endl;
pthread_exit(NULL);
}
输出结果
main() : creating thread, 0
main() : creating thread, 1
main() : creating thread, 2
main() : creating thread, 3
main() : creating thread, 4
Sleeping in thread
Sleeping in thread
Sleeping in thread
Sleeping in thread
Sleeping in thread
pthread_join get status:10
pthread_join get status:11
pthread_join get status:12
pthread_join get status:13
pthread_join get status:14
Main: program exiting.
互斥锁机制
#include<bits/stdc++.h>
using namespace std;
const int maxn=5;
int sum=0; //创建全局变量 所有线程同时写 需要锁机制
pthread_mutex_t sum_mutex;//创建互斥锁
void* say(void *args)
{
cout<<"hello in thread "<< *((int*)args)<<endl;
pthread_mutex_lock(&sum_mutex);//加锁 使得其他进程无法同时修改sum值
cout<<"before sum is "<<sum<<" in thread "<< *((int*)args)<<endl;
sum+= 5;
cout<<"after sum is "<<sum<<" in thread "<<*((int*)args)<<endl;
pthread_mutex_unlock(&sum_mutex);//释放锁 供其他线程使用
pthread_exit(0);
}
int main()
{
pthread_t tids[maxn];
int idx[maxn];
pthread_attr_t attr;//创建线程属性
pthread_attr_init(&attr);//初始化
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE);
//设置为连接属性
pthread_mutex_init(&sum_mutex,NULL);//初始化锁
for(int i=0;i<maxn;i++)
{
idx[i]=i;
int rc=pthread_create(&tids[i],&attr,say,(void*)(&idx[i]));
if(rc) cout<<"thread error:"<<rc<<endl;
}
pthread_attr_destroy(&attr);//释放内存
void *status;
for(int i=0;i<maxn;i++)
{
int rc=pthread_join(tids[i],&status);
if(rc) cout<<"pthread_join error"<<endl;
}
cout<<"finally sum is= "<<sum<<endl;
pthread_mutex_destroy(&sum_mutex);//注销锁
}
互斥锁是实现线程同步的一种机制,只要在临界区前后对资源加锁就能阻塞其他进程的访问。
hello in thread 0
before sum is 0 in thread 0
after sum is 5 in thread 0
hello in thread 1
before sum is 5 in thread 1
after sum is 10 in thread 1
hello in thread 2
before sum is 10 in thread 2
after sum is 15 in thread 2
hello in thread 3
before sum is 15 in thread 3
after sum is 20 in thread 3
hello in thread 4
before sum is 20 in thread 4
after sum is 25 in thread 4
finally sum is= 25
运气比较好,每次的线程都是按顺序运行,理论上可以出现线程运行顺序不同的情况,但是sum的修改过程是一定按照顺序来的
信号量
介绍信号量前先介绍几种基础的操作
pthread_cond_t sign//新建信号量
pthread_cond_wait(&tasks_cond,&tasks_mutex);
主要介绍一下该函数,该函数有两个参数,第一个参数为信号量,它首先将当前线程加入到唤醒队列,然后旋即解锁mutex锁,最后等待被唤醒。被唤醒后,又对mutex加锁(可能是看起来没有对用户的行为作任何的改变)。
这样的话我们使用这个函数前就需要对mutex加锁,防止出现多线程等待的情况。
#include<bits/stdc++.h>
using namespace std;
const int maxn=5;
int tasks=10;
pthread_mutex_t tasks_mutex;//互斥锁
pthread_cond_t tasks_cond; //条件信号变量 tasks大于5 say_2处理 否则 1处理
void* say_2(void *args)
{
pthread_t pid=pthread_self();//获取当前线程id
cout << "[" << pid << "] hello in thread " << *( ( int* )args ) << endl;
bool sign=false;//sign
while(1)
{
pthread_mutex_lock(&tasks_mutex);//加锁
if(tasks>maxn)
{
cout << "[" << pid << "] take task: " << tasks << " in thread " << *( (int*)args ) << endl;
--tasks;
}
else if(!sign)
{
cout << "[" << pid << "] pthread_cond_signal in thread " << *( ( int* )args ) << endl;
pthread_cond_signal(&tasks_cond);//发送信号 表示已经大于5
sign=true;//信号已发送 退出该线程
}
pthread_mutex_unlock(&tasks_mutex);//解锁
if(tasks==0) break;
}
pthread_exit(0);
}
void* say_1(void *args)
{
pthread_t pid=pthread_self();
cout << "[" << pid << "] hello in thread " << *( ( int* )args ) << endl;
while(1)
{
pthread_mutex_lock(&tasks_mutex);//加锁
if(tasks>maxn)
{
cout << "[" << pid << "] pthread_cond_signal in thread " << *( ( int* )args ) << endl;
pthread_cond_wait(&tasks_cond,&tasks_mutex);
cout<<"---"<<endl;
//等待信号量生效 接收到信号 向say_2发送信号 跳出wait等待后续
}
else
{
cout << "[" << pid << "] take task: " << tasks << " in thread " << *( (int*)args ) << endl;
--tasks;
}
pthread_mutex_unlock(&tasks_mutex);//解锁
if(tasks==0) break;
}
pthread_exit(0);
}
int main()
{
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE);
pthread_cond_init(&tasks_cond,NULL);
pthread_mutex_init(&tasks_mutex,NULL);
pthread_t pid1,pid2;
int idx1=1;
int rc=pthread_create(&pid1,&attr,say_1,(void*)&idx1);
if(rc) cout<<"error!"<<endl;
int idx2=2;
rc=pthread_create(&pid2,&attr,say_2,(void*)&idx2);
if(rc) cout<<"error!"<<endl;
pthread_join(pid1,NULL);//连接两个线程
pthread_join(pid2,NULL);
pthread_attr_destroy(&attr);
pthread_mutex_destroy(&tasks_mutex);
pthread_cond_destroy(&tasks_cond);
}
[140737348364032] hello in thread 1
[140737348364032] pthread_cond_signal in thread 1
[140737339971328] hello in thread 2
[140737339971328] take task: 10 in thread 2
[140737339971328] take task: 9 in thread 2
[140737339971328] take task: 8 in thread 2
[140737339971328] take task: 7 in thread 2
[140737339971328] take task: 6 in thread 2
[140737339971328] pthread_cond_signal in thread 2
---
[140737348364032] take task: 5 in thread 1
[140737348364032] take task: 4 in thread 1
[140737348364032] take task: 3 in thread 1
[140737348364032] take task: 2 in thread 1
[140737348364032] take task: 1 in thread 1
通过上述代码可以发现,开始运行线程1,然后运行到pthread_cond_wait(&tasks_cond,&tasks_mutex);,该线程进入唤醒状态,等待信号量生效,同时从该位置终止线程,进入线程2,完成任务后(即tasks小于5),发送信号量,同时该线程2结束,进入线程1等待的位置重新运行。