目录
- 线程终止
- return 退出
- pthread_exit函数终止线程
- pthread_cancel强制终止线程
- 进程终止
- 线程等待(pthread_join)
- 分离线程(pthread_detach)
- 创建多线程
- 线程的局部存储
线程的创建已经谈论过,还有线程的终止,等待和分离。
线程终止
终止线程的三种方式:
正常退出:
- 线程执行完它的函数之后
return
自动结束 - 线程显示调用
pthread_exit
函数退出
强制终止:
- 一个线程可以被另一个线程通过
pthread_cancel
函数强制退出
进程终止:
- 一个线程终止,那么该进程的所有线程都会终止
return 退出
#include <pthread.h>
#include <iostream>
#include <unistd.h>
#include <string.h>void *rout(void *arg)
{int cnt = 5;while (cnt--){sleep(2);std::cout << "thread id : " << pthread_self() << " i am thread num: " << *(int *)arg << std::endl;}return NULL;
}int main()
{pthread_t tid;int num = 10;int res = pthread_create(&tid, NULL, rout, (void *)(&num));if (res != 0){fprintf(stderr, "pthread_create: %s\n", strerror(res));exit(1);}while (true){std::cout << "tid: " << tid << " I am main thread, my tid:" << pthread_self() << std::endl;sleep(1);}return 0;
}
pthread_exit函数终止线程
关于exit:专门用来终止进程的,不能用来终止线程!任意一个线程调用exit都表示进程终止!如果想让一个线程马上终止,就要用到接口:pthread_exit
。
void pthread_exit(void *retval);
retval
:这是一个指向任意数据的指针,当线程调用 pthread_exit 时,retval 指向的值会被设置为该线程的退出状态。并且可以被其他线程通过调用 pthread_join
来访问。
#include <pthread.h>
#include <iostream>
#include <unistd.h>
#include <string.h>
using namespace std;void *rout(void *arg)
{int cnt = 3;int *status = new int(20);while (true){cout << "thread id : " << pthread_self() << " i am thread num: " << *(int *)arg << endl;sleep(1);if (cnt == 0){cout << "phread exit....." << endl;pthread_exit((void *)(status));}cnt--;}return NULL;
}int main()
{pthread_t tid;int num = 10;int res = pthread_create(&tid, NULL, rout, (void *)(&num));void *status = NULL;pthread_join(tid, &status); // 等待线程结束sleep(5);cout << "wait success! main thread end! status: " << *(int *)(status) << endl;return 0;
}
需要注意,pthread_exit或者return返回的指针指向的内存单元应该是全局的,因为线程终止之后其函数栈帧会销毁,之后才会返回一个void*指针。
pthread_cancel强制终止线程
功能:取消一个执行中的线程
int pthread_cancel(pthread_t thread);
thread
表示要取消的线程id(用户级)
成功返回0,否则返回错误码
#include <pthread.h>
#include <iostream>
#include <unistd.h>
#include <string.h>
using namespace std;void *rout(void *arg)
{int cnt = 3;int *status = new int(20);while (true){cout << "thread id : " << pthread_self() << " i am thread num: " << *(int *)arg << endl;sleep(1);}return NULL;
}int main()
{pthread_t tid;int num = 10;int res = pthread_create(&tid, NULL, rout, (void *)(&num));void *status = NULL;sleep(5);pthread_cancel(tid); // 强制终止tidcout << "thread is end" << endl;// pthread_join(tid, &status); // 等待线程结束sleep(3);cout << "wait success! main thread end! status: " << *(int *)(status) << endl;return 0;
}
即便在之前对线程发出了取消请求,仍然很重要的是调用 pthread_join ,以确保所有相关资源被清理,避免资源泄漏和僵尸线程的产生。在多线程环境中正确管理线程的生命周期和资源是程序健壮性的重要一环。也就是说,线程取消也需要被join。
进程终止
一旦某个线程调用exit或者异常而终止线程,整个进程都会终止,因为操作系统发信号是给进程发信号。
线程等待(pthread_join)
为什么需要等待线程?
之前也说过,其实已经退出的线程并没有完全“结束”,其栈帧并没有随着线程终止马上就释放,仍然在进程的地址空间里。并且,如果不对这些已经终止但是还没有被释放空间的线程做处理,往后继续创建新线程都不会复用前面退出线程的地址空间,这就造成了资源的浪费。这种情况其实非常像我们之前谈过的僵尸进程问题。
等待线程终止就是提醒内核可以释放这个线程的资源了。 等待线程终止其实也是为了确保线程完成任务。有时主线程或者其他线程需要等待某一个线程任务完成之后才能继续执行。等待一个线程结束,其实就是让终止的线程退出时“通知”一下其它线程,可以不关心退出线程的返回结果。此外,等待线程终止在某些情况下能保证数据的完整性,比如线程一处理上半段数据,线程二处理下半段数据,如果其中任何一个线程没有终止,那总数据就会不完整。
为了实现线程终止时的等待问题,linux提供了pthread_join
函数。
作用:阻塞调用该函数线程,直到目标线程终止。
函数原型:
#include<pthread.h>
int pthread_join(pthread_t thread, void** retval);
thread
:这是要等待的线程的标识符(ID),该标识符是由 pthread_create 函数返回的。
retval
这是一个指向 void *
指针的指针,用于接收被等待线程的返回值。如果被等待的线程调用了 pthread_exit
并传递了一个返回值,或者简单地返回了一个值(对于从 void* 返回类型的线程函数),那么这个值就可以通过这个参数返回给等待的线程。如果对这个返回值不感兴趣,可以传递 NULL。
pthread_join
就是将pthread_exit
返回信息的地址写入了pthread_join
的retval
参数。
如果成功,pthread_join 返回 0;如果失败,则返回错误码。
值得注意的是,线程终止方式的不同,其通过pthread_join得到的退出信息也就不同。比如:
目标线程通过return 终止(或者是pthread_exit),retval所指向的单元里存放的就是目标线程执行函数的返回值。
观察下面代码,分析线程return终止时,pthread_join得到的返回值(pthread_exit函数返回在上面的代码有演示)
#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>void *threadrun(void *args)
{int cnt = 3;int* i = new int(123);while(cnt){std::cout<<"new thread run ...,cnt: "<<cnt--<<std::endl;sleep(1);}return (void*)i;
}
int main()
{pthread_t tid;int n = pthread_create(&tid,nullptr,threadrun,(void*)"thread 1");std::cout<<"main thread join begin..."<<std::endl;void *status = NULL; n= pthread_join(tid, &status);if(n==0){std::cout<<"main thread wait success"<< "i: " << *(int*)status << std::endl;}return 0;
}
如果线程是被别的线程通过调用pthread_cancel
函数强制终止掉,retval所指向单元存放的就是常数PTHREAD_ CANCELED
。
PTHREAD_ CANCELED
是一个宏
#define PTHREAD_CANCELED ((void *) -1)
#include <pthread.h>
#include <iostream>
#include <unistd.h>
#include <string.h>
using namespace std;void *rout(void *arg)
{int cnt = 3;int *status = new int(20);int num = 20;while (cnt--){cout << "thread id : " << pthread_self() << " i am thread num: " << *(int *)arg << endl;sleep(1);}pthread_exit((void *)status);// return (void *)status;
}int main()
{pthread_t tid;int num = 10;int res = pthread_create(&tid, NULL, rout, (void *)(&num));void *status = NULL;sleep(2);pthread_cancel(tid);pthread_join(tid, &status); // 等待线程结束if (status == PTHREAD_CANCELED){cout << "pthread is cancel " << endl;}return 0;
}
retval所指向单元存放的就是常数PTHREAD_ CANCELED
得证。
主线程和新线程谁先运行并不确定,但是我们总是希望主线程最后退出,所以就需要调用 pthread_join
。如果不使用join可能造成僵尸线程(类似僵尸进程)的问题。
分离线程(pthread_detach)
分离线程实际上是线程的一种状态,这种状态表示该进程不需要被等待,且线程退出后会自动释放资源。 对于主线程来说,有些线程独立执行任务,其它线程没有必要再调用pthread_join阻塞等待,但是他仍然是进程的一部分。这样提升了整体的效率。一般来说,创建的新线程默认都是joinable的,也就是需要被等待的,我们可以通过pthread_detach
函数来改变这一性质。
pthread_detach
函数
功能:分离一个目标线程,使该线程终止后自动释放资源,不需要被等待,而且也不能被等待。
#include<pthread.h>
int pthread_detach(pthread_t thread);
thread
表示分离的目标线程,也可以是调用该函数的线程本身
成功放回0,否则-1
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
using namespace std;void *thread_run(void *arg)
{pthread_detach(pthread_self());printf("%s\n", (char *)arg);return NULL;
}int main(void)
{pthread_t tid;if (pthread_create(&tid, NULL, thread_run, (void *)"thread1 run...") != 0){printf("create thread error\n");return 1;}int ret = 0;sleep(1); // 很重要,要让线程先分离,再等待if (pthread_join(tid, NULL) == 0){printf("pthread wait success\n");ret = 0;}else{printf("pthread wait failed\n");ret = 1;}return ret;
}
因为执行thread_run函数的线程设置成了分离状态,函数结束之后自动释放资源。此时再去用pthread_join函数去等待这个线程就会得到返回值就不再是0。
如果在线程分离的情况下,且主线程没有做等待,新线程出错了,整个进程也是直接挂掉的,因为它还是在进程内部。
创建多线程
创建线程id和线程name,保存所有线程的id信息,最后主线程回收每个线程
#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>const int num = 10;void *threadrun(void *args)
{std::string name = static_cast<const char*>(args);while(true){std::cout << name << " is running" << std::endl;sleep(1);break;}return args;
}
int main()
{std::vector<pthread_t> tids;for(int i = 0; i < num; i++){// 1. 有线程的idpthread_t tid;// 2. 线程的名字char *name = new char[128];snprintf(name, 128, "thread-%d", i+1);pthread_create(&tid, nullptr, threadrun, /*线程的名字*/name);//3.保存所有线程idtids.push_back(tid);}for(auto tid:tids){void* name = nullptr;pthread_join(tid, &name);std::cout<<(const char*)name << "quit..." << std::endl;delete (const char*)name;}
}
线程的局部存储
我们知道新线程(对gval做++)和主线程都在打印全局变量gval的值和地址,一旦全局变量被修改两者是都能看见的,因为都在一个进程内,共享一个地址空间。这种全局变量本身就是多线程之间共享的。
如果一个全局的变量需要让每个线程私有需要加__thread
__thread int gval=100;
#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>__thread int gval=100;std::string ToHex(pthread_t tid)
{char id[128];snprintf(id, sizeof(id), "0x%lx", tid);return id;
}
void*threadrun(void *args)
{std::string name =static_cast<const char*>(args);while(true){std::string id = ToHex(pthread_self());std::cout << name << " is running, tid: " << id << ", gval: " << gval << ", &gval: " << &gval << std::endl;gval++;sleep(1);}
}
int main()
{pthread_t tid;pthread_create(&tid,nullptr,threadrun,(void*)"thread-1");while(true){std::cout << "main thread, gval: " << gval << ", &gval: " << &gval << std::endl;sleep(1);}pthread_join(tid,nullptr);return 0;
}
看效果:此时新线程中和主线程中的gval值不一样地址也不一样,显然它们用的gval不是同一个了。
当使用了__thread关键字后,GCC会在每个线程的上下文中为该变量创建一个独立的实例。这样,每个线程都可以独立地修改其对应的变量实例,而不会影响到其他线程。并且,被__thread修饰的变量会被存储在各自线程的局部存储中,这种存储方式确保了线程间的数据隔离。__thread只在Linux下有效,而且只能修饰内置类型。