2.3 🍎线程退出🍎
🍋pthread_join🍋
但是其实上面的代码中还存在一个很严重的问题,我们在学习进程中知道,父进程会wait子进程,否则就可能造成了内存泄漏。线程也是一样的,已经退出的线程,其空间没有被释放,仍然在进程的地址空间内;创建新的线程不会复用刚才退出线程的地址空间。主线程必须要回收其他线程的资源,否则就会造成内存泄漏,那回收其他线程的接口是啥呢?
我们来看看官网对pthread_join的介绍:
功能:等待线程结束
原型:
int pthread_join(pthread_t thread, void **value_ptr);
参数:
thread:线程ID
value_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码
第一个参数比较好理解,那么第二个参数是一个二级指针,接受的是一个线程的返回值,那么我们知道创建线程的参数里面有一个函数指针,该函数指针的返回值为void*,而这个返回值就可以传递给join的第二个参数使用,比如我们看看下面的代码:
void *Run(void *args) { const char *name = static_cast<char *>(args); cout << "thread1 is running" << endl; sleep(2); return (void *)11; } int main() { pthread_t p1; pthread_create(&p1, nullptr, Run, nullptr); cout << "I am is main thread,is running" << endl; sleep(2); void* ret=nullptr; pthread_join(p1,&ret); cout<<"new pthread exit "<<ret<<endl; return 0; }
当我们运行时:
我们发现我们通过返回值返回的11被join给接收到了。
🍋pthread_exit🍋
除了使用return 这种方式,我们还可以使用哪种方式终止线程呢?我们还可以使用pthread_exit
接口来处理:
功能:线程终止
原型:
void pthread_exit(void *value_ptr);
参数:
value_ptr:value_ptr不要指向一个局部变量。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)
当我们这样使用时:
我们可以来观察下运行结果:
🍋pthread_cancel🍋
除了上面我们讲解的这两种方式外,我们还可以使用pthread_cancel
取消一个执行中的线程:
功能:取消一个执行中的线程
原型:
int pthread_cancel(pthread_t thread);
参数:
thread:线程ID
返回值:成功返回0;失败返回错误码
假如我们想要取消自己呢?我们如何得到自己的pid,我们可以使用pthread_self()
:
我们下面来看看线程取消的基本用法:
void *threadRun(void* args) { const char*name = static_cast<const char *>(args); int cnt = 5; while(cnt) { cout << name << " is running: " << cnt-- << " obtain self id: " << pthread_self() << endl; sleep(1); } pthread_exit((void*)11); // PTHREAD_CANCELED; #define PTHREAD_CANCELED ((void *) -1) } int main() { pthread_t tid; pthread_create(&tid, nullptr, threadRun, (void*)"thread 1"); sleep(3); pthread_cancel(tid); void *ret = nullptr; pthread_join(tid, &ret); cout << " new thread exit : " << (int64_t)ret << "quit thread: " << tid << endl; return 0; }
当我们直接运行时:
不难发现程序3s后直接退出了,其实也很好理解,因为我们退出的是主线程,所以肯定会直接退出的。
所以我们可以总结线程退出有三种方式:
- 1️⃣从线程函数return,这种方法对主线程不适用,从main函数return相当于调用exit。
- 2️⃣线程可以调用pthread_ exit终止自己。
- 3️⃣一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。
- 调用pthread_join函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:
- 1️⃣如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
2️⃣ 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_ CANCELED(-1)。
3️⃣ 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
4️⃣ 如果对thread线程的终止状态不感兴趣,可以传nullptr给value_ ptr参数。
2.4 🍎分离线程🍎
- 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
- 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
- 所以此时我们可以使用
pthread_detach
:
可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离:
pthread_detach(pthread_self());
joinable和分离是冲突的,一个线程不能既是joinable又是分离的。
我们来验证下:
void* Run(void* args) { pthread_detach(pthread_self()); const char* name=static_cast<char*> (args); cout<<name<<" is running"<<endl; return nullptr; } int main() { pthread_t p1; pthread_create(&p1,nullptr,Run,(void*)"thread1"); int ret=pthread_join(p1,nullptr); if(ret==0) cout<<"wait success"<<endl; else cout<<"wait fail "<<endl; return 0; }
当我们运行时:
为什么是运行success呀?不是说joinable和分离是冲突的吗?按道理这里应该会join失败的呀。
这是由于执行时是先执行的join,此时线程还没有被分离,自然就能够join成功了,我们可以像下面这样写,就会join失败:
当我们再次运行时:
2.5 🍎理解线程独立栈🍎
首先我们来看看一张图:
通过之前动静态库的知识我们知道,pthread库是加载到共享区的,那么也就决定了进程中所有线程都是可以访问得到该库的。但是从上图我们看见了有一个主线程栈的空间,这个空间又是为谁准备的呢?
其实这个空间是为主线程准备的,我们之前讲过其余线程中的栈是相互独立的,而这个独立栈的空间就开辟在共享区中,也就是独立栈的空间其实是由库帮助我们开辟的。上图右边第一个struct_pthread又是什么鬼呢?这个是管理共享区中线程的一种数据结构,类似于进程中的PCB。至于什么是局部存储,我们可以来写一个程序看看:
int g_val=20; void* Run(void* args) { const char* name=static_cast<char*> (args); while(true) { cout<<"g_val:"<<g_val<<"&g_val:"<<&g_val<<endl; sleep(1); } } int main() { pthread_t pids[5]; for(int i=0;i<5;++i) { char* name=new char[32]; snprintf(name,32,"pthread%d:",i+1); pthread_create(pids+i,nullptr,Run,name); } for(int i=0;i<5;++i) { pthread_join(pids[i],nullptr); } return 0; }
当我们运行时:
这也符合我们的预期,因为全局变量是所有线程共享的,但是当我们在全局变量前加上了__pthread
后:
当我们运行时:
我们惊奇的发现居然地址不一样了,这其实就是将g_val分别保存了一份在各自的独立栈中。至于为什么打印出来的数据无规律是因为多线程并发访问的问题,我们后面在详细讲解。