目录
一、线程创建
1.1 pthread_create
1.2 线程传入启动函数参数方式
二、线程退出(pthread_exit函数 pthread_cancel函数)
三、线程等待
3.1 为什么要线程等待?
3.2 pthread_join函数
四、线程分离
4.1 pthread_detach() 和 pthread_self()
五、pthread库维护线程的基本结构
一、线程创建
1.1 pthread_create
thread:返回线程ID
attr:设置线程的属性,attr为NULL表示使用默认属性
start_routine:是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码
1.2 线程传入启动函数参数方式
关于第四个参数,我们要注意其是void*类型指针,任意指针都可以传给线程!现在我们模拟一种错误的传参方式,传栈区资源,基于线程运行顺序产生的传参问题!
#define NUM 5
void* pthread_route(void* args)
{const char* name = (const char*)args;cout << "new thread create success, its name: "<<name<<endl;
}int main()
{pthread_t pid;for(int i=0;i<NUM;i++){char buffer[64];snprintf(buffer,sizeof(buffer),"thread %d",i+1);pthread_create(&pid,nullptr,pthread_route,buffer);}sleep(1);return 0;
}
运行结果:
结果显示,我们的线程名字都是一样的!
首先我们可以知道这样的结果说明我们传过去的buffer地址是同一个地址! 有人可能疑惑不是每次循环后buffer的地址应该每次不一样吗? 这里结合函数栈帧知识,发现并没有函数调用,所以每次都还是在main函数栈帧内,所以每次的地址其实循环进来依旧是原来的地址!
有人又会提出,不对啊,按道理应该数字是根据i+1的来啊,按照我们route函数思路不应该全是5啊!
这就说明事实不是按照我们的所想,main函数创建线程后并不是立马执行线程对应的函数,当main函数创建完所有线程后,才执行线程的代码!而此时配合前面buffer首地址没变,而里面的数据再不断变换导致!
所以我们创建线程如果想看到线程私有的东西,不能传入共享的数据!我们不能传入栈内资源(这里没有多想其他场景),所以我们应该利用堆区资源!
正确的传参方式:数据放入堆区,传堆区指针!
#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
#define NUM 3
using namespace std;//每个线程独立的资源 线程ID + 线程buffer
class Thread_data
{
public:pthread_t _pid;char namebuffer[64];
};void *thread_route(void *args)
{Thread_data *p = static_cast<Thread_data *>(args);int cnt = 3;while (cnt--){cout << "new thread create success,its name: " << p->namebuffer << " &cnt: " << &cnt <<" cnt= "<<cnt<<endl;}delete p;return nullptr;
}int main()
{for (int i = 0; i < NUM; i++){Thread_data *pt = new Thread_data();snprintf(pt->namebuffer, sizeof(pt->namebuffer), "thread %d", i + 1);pthread_create(&pt->_pid, nullptr, thread_route, pt);}sleep(1);return 0;
}
运行结果:
二、线程退出(pthread_exit函数 pthread_cancel函数)
void pthread_exit(void *value_ptr);
参数
value_ptr:value_ptr不要指向一个局部变量。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)
pthread_cancel 功能:取消一个执行中的线程
int pthread_cancel(pthread_t thread);
参数
thread:线程ID
返回值:成功返回0;失败返回错误码
三、线程等待
3.1 为什么要线程等待?
为什么要有线程等待?回想以前的进程等待,父进程回收子进程,获取子进程退出信息!
对于已经退出的线程,其空间没有被释放,仍在进程的地址空间内!所以创造新的线程不会复用刚才退出线程的地址空间,这就造成了资源的浪费!
3.2 pthread_join函数
原型:
int pthread_join(pthread_t thread, void **value_ptr);
参数:
thread:线程ID
value_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码
线程的调用逻辑函数返回值是void*,该函数会在线程结束前将线程结束状态码保存进pthread库中,打个比方它的数字状态码是10,然后通过强转(void*)10,将4字节整形放入8字节的指针中(Linux下指针默认是64位下!)。外部获取函数内部结果,我们需要传地址,所以要传void**!
需要知道的是,我们返回的值一定是右值!也就输说常变量、堆空间资源可以返回!栈上数据不能返回因为函数栈帧结束后栈空间数据会被销毁!
#include<iostream>
#include<unistd.h>
#include<pthread.h>
#include<vector>using namespace std;
#define NUM 5
class pthread_data
{
public:pthread_t tid;char buffer[128];
};
class Thread_ret
{
public:int exit_code;int exit_result;
};void* pthread_route(void* args)
{Thread_ret* ret = new Thread_ret();int cnt = 5;while(cnt){cout <<"cnt: " << cnt-- <<" &cnt:" <<&cnt<<endl;//BUG?sleep(1);}ret->exit_code = 106;ret->exit_result = 0;return (void*)ret;
}
int main()
{vector<pthread_data*> vp;for(int i=0;i<NUM;i++){pthread_data* pd = new pthread_data();pthread_create(&pd->tid,nullptr,pthread_route,pd);snprintf(pd->buffer,sizeof(pd->buffer),"thread :%d pid:0x%x ",i+1,pd->tid);vp.push_back(pd);}for(auto& e : vp){cout << "creat thread success: " << e->buffer<<endl;}for(auto&e :vp){Thread_ret* ret;pthread_join(e->tid,(void**)(&ret));cout <<"join :" <<e->buffer<<" success " <<"exit code:" <<ret->exit_code <<" exit result: " <<ret->exit_result<<endl;delete e;}cout <<"main quit!"<<endl;return 0;
}
四、线程分离
1.默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
2.如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
4.1 pthread_detach() 和 pthread_self()
分离线程,可以是指定的其他线程,也可以是自己!
int pthread_detach(pthread_t thread);
pthread_self()获取线程自己的ID!
五、pthread库维护线程的基本结构
在前面我们了解到线程有属于自己的ID、私有栈等等结构,这些线程肯定需要被组织!如何组织呢?在Linux下线程库内用结构体(TCP)对每个线程进行组织!库其实本质是磁盘上的文件,在链接的时候将内容加载到内存的共享区内,也就是说我们的线程的属性存储在共享区内!线程的私有栈区实在共享区内!
线程的局部存储:可以使一个全局变量让所有线程私有栈内创建一份!
在全局变量前面加上__thread 即可 !