Linux中,新建线程并不在原先进程中,而是通过一个系统调用 clone() 创建另一个执行环境
该系统调用clone() copy了一个和原先进程完全一样的进程,并在这个进程中执行线程函数。
该copy过程和fork不一样,copy后的进程和原先的进程共享了所有的变量和运行环境。
【pthread_join】
一个线程等待另一个线程结束
代码中如果没有pthread_join主线程会很快结束从而使整个进程结束,
从而使创建的线程没有机会开始执行就结束了。
加入pthread_join后,主线程会一直等待直到等待的线程结束自己才结束,
使创建的线程有机会执行。
所有线程都有一个线程号,也就是Thread ID。其类型为pthread_t。
通过调用pthread_self()函数可以获得自身的线程号。
如果主线程在其他线程退出之前就已经退出,则带来的bug不可估量。
通过 pthread_join 会让主线程阻塞,直到所有线程退出。
int pthread_join(pthread_t thread, void **value_ptr);
thread:等待退出线程的线程号
value_ptr:退出线程的返回值
在多线程编程时都是以for循环的形式调用 pthread_join ,
既然运行 prhtead_join 后主线程就阻塞了,也没法调用后面的 pthread_join ,
for循环作用:
主线程是在第一个线程处挂起
比如有:
pthread_join(1,NULL);
pthread_join(2,NULL);
pthread_join(3,NULL);
pthread_join(4,NULL);
pthread_join(5,NULL);
实际上主线程在 pthread_join(1,NULL) 这里就挂起,等待1号线程结束后再等待2号线程。
当然会出现3,4,5比1,2先结束的情况。
主线程还是在等待1,2结束后,发现3,4,5早结束了,就会回收3,4,5的资源,然后主线程再退出。
通过 pthread_join() 来使主线程阻塞等待其他线程退出,主线程就可以清理其他线程的环境。
【pthread_detach】
还有一些线程,喜欢自己清理退出状态,不愿意主线程调用 pthread_join 等待。
将这一类线程的属性称为 detached
如果调用 pthread_create() 时将属性设为NULL,则表明所创建的线程用默认属性:joinable。
如果将属性设为 detached ,则应执行如下设定:
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&pthreadid, &attr, myprocess, &arg);
在线程设置为 joinable 后,可以调用 pthread_detach() 使之成为 detached
相反的操作不可以
如果线程已经调用 pthread_join() 后,再调用pthread_detach() 不会有任何效果
【线程的结束】
线程可以通过自身执行结束来结束,
也可以通过调用 pthread_exit() 结束线程的执行。
线程甲可以被线程乙被动结束(通过调用 pthread_cancel())
int pthread_cancel(pthread_t thread);
成功返回0
线程也不是被动的被别人结束。
它可以通过设置自身的属性来决定如何结束
线程的被动结束分为两种:
(1)异步终结
(2)同步终结
异步终结就是当其他线程调用 pthread_cancel 时,线程立刻被结束。
同步终结则不会立刻终结,它会继续运行,直到到达下一个结束点(cancellation point)
这里的结束点就是下一次系统调用。
当一个线程被按照默认的方式创建,它的属性是同步终结。
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
pthread_t tid[2];
void *recv_thread(void *arg)
{
int cnt = 1;
printf("recv thread, enter ...\n");
while(1) {
cnt++;
}
printf("recv thread, exit ...\n");
pthread_exit(NULL);
}
void *send_thread(void *arg)
{
printf("send thread, enter ...\n");
sleep(5);
printf("send thread, before cancel ...\n");
pthread_cancel(tid[0]);
printf("send thread, after cancel ...\n");
printf("send thread, exit ...\n");
pthread_exit(NULL);
}
int main(int argc, const char *argv[]) {
int ret;
printf("main thread, enter ...\n");
ret = pthread_create(&tid[0], NULL, recv_thread, NULL);
if(ret != 0) {
printf("main thread fail to pthread_create recv_thread, %s\n",strerror(errno));
exit(EXIT_FAILURE);
}
printf("main thread, pthread_create recv_thread successful\n");
ret = pthread_create(&tid[1], NULL, send_thread, NULL);
if(ret != 0) {
printf("main thread, fail to pthread_create send_thread, %s\n",strerror(errno));
exit(EXIT_FAILURE);
}
printf("main thread, pthread_create send_thread successful\n");
printf("main thread, before join\n");
pthread_join(tid[0],NULL);
pthread_join(tid[1],NULL);
printf("main thread, after join\n");
exit(EXIT_SUCCESS);
}
//======================================================================
void *recv_thread(void *arg)
{
int cnt = 1;
printf("recv thread, enter ...\n");
while(1) {
cnt++;
sleep(2); //没问题
}
printf("recv thread, exit ...\n");
pthread_exit(NULL);
}
//======================================================================
void *recv_thread(void *arg)
{
int cnt = 1;
printf("recv thread, enter ...\n");
while(1) {
cnt++;
sleep(10); // 有问题,main 可以退出,recv 无法打印后续内容
}
printf("recv thread, exit ...\n");
pthread_exit(NULL);
}
//======================================================================
作为sleep的替代方案,可以使用 pthread_cond_timedwait ,要退出时,向条件变量发出信号.
//======================================================================
调用pthread_testcancel,让内核去检测是否需要取消当前线程。
被取消的线程,退出值是 PTHREAD_CANCELED (-1)
//======================================================================
main 完全能等到 sleep(xxx) 的线程执行完毕
但是 send_thread 给睡眠的 recv_thread 发 cancel 时,却等不大 recv_thread 结束
就直接退出
查阅资料发现,pthread_cancel 就是一个封装的发信号
发送的信号是SIGCANCEL,这个是posix库Linux下NPTL线程库使用的一个内部信号
一个更加精确制导的信号投递
所以个人理解:
sleep 的 recv_thread 会响应该信号,然后直接在 kernel 退出
留下 main 去收用户态的 shiti
//======================================================================
【 博友的一个错误 】
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>
void mythread() {
int i,ret;
ret = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
if(ret != 0) {
printf("Thread pthread_setcancelstate faild.");
exit(1);
}
ret = pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);
if(ret != 0) {
printf("Thread pthread_setcanceltype faild.");
exit(1);
}
for(i=0;i<10;i++) {
printf("Thread is running(%d)....\n",i);
sleep(1);
}
pthread_exit("hello world");
}
int main() {
pthread_t id;
int ret;
void *thread_result;
ret = pthread_create(&id,NULL,(void*)mythread,NULL);
if(ret != 0) {
printf("Create pthread error.\n");
exit(1);
}
sleep(3);
printf("Canceling thread...\n");
ret = pthread_cancel(id);
if(ret != 0) {
printf("Thread cancelation failed.\n");
exit(1);
}
sleep(2);
pthread_join(id,&thread_result);
// 用 gdb 调试发现最后 core dump 在这里
printf("Thread jointed it returned:%s",(char*)thread_result);
return 0;
}
(gdb)
[Switching to thread 1 (Thread 0x7fd90989b700 (LWP 5727))]
#0 0x00007fd9090f9cd0 in _IO_vfprintf_internal (s=0x7fd909470620 <_IO_2_1_stdout_>,
format=<optimized out>, ap=ap@entry=0x7ffe6b27e4a8) at vfprintf.c:1632
1632 in vfprintf.c
屏蔽掉之后就OK了,具体原因:
int main() {
pthread_t id;
int ret;
char str[20];
void *str1 = (void *)&str;
ret = pthread_create(&id,NULL,(void*)mythread,NULL);
if(ret != 0) {
printf("Create pthread error.\n");
exit(1);
}
sleep(3);
printf("Canceling thread...\n");
ret = pthread_cancel(id);
if(ret != 0) {
printf("Thread cancelation failed.\n");
exit(1);
}
sleep(2);
pthread_join(id,&str1);
printf("Thread jointed it returned:%s",str); // 至少这里不报错了
return 0;
}
pthread_join 的第二个参数需要的是 void** 类型的
传递双指针(指针的地址)的目的在于修改指针的值,也就是让指针指向别的地方
所以这里就算 str1 = (void *)&str 也是没用的,本来指向 str ,
结果 pthread_join 让 str1 指向了别的地方。
//======================================================
The pthread_exit() function terminates the calling thread and returns a value via retval that (if the thread is joinable) is available to another thread in the same process that calls pthread_join(3).
Any clean-up handlers established by pthread_cleanup_push(3) that have not yet been popped, are popped (in the reverse of the order in which they were pushed) and executed. If the thread has any thread-specific data, then, after the clean-up handlers have been executed, the corresponding destructor functions are called, in an unspecified order.
When a thread terminates, process-shared resources (e.g., mutexes, condition variables, semaphores, and file descriptors) are not released, and functions registered using atexit(3) are not called.
After the last thread in a process terminates, the process terminates as by calling exit(3) with an exit status of zero; thus, process-shared resources are released and functions registered using atexit(3) are called.
Performing a return from the start function of any thread other than the main thread results in an implicit call to pthread_exit(), using the function’s return value as the thread’s exit status.
To allow other threads to continue execution, the main thread should terminate by calling pthread_exit() rather than exit(3).
The value pointed to by retval should not be located on the calling thread’s stack, since the contents of that stack are undefined after the thread terminates.
Currently, there are limitations in the kernel implementation logic for wait(2)ing on a stopped thread group with a dead thread group leader. This can manifest in problems such as a locked terminal if a stop signal is sent to a foreground process whose thread group leader has already called pthread_exit().
pthread_exit()
终止线程并通过 retval 返回一个值(如果该线程是 joinable 的)
可用于同一进程中调用 pthread_join() 的另一个线程。
任何由 pthread_cleanup_push() 建立
但尚未弹出的清理处理程序都将被弹出(与push它们的顺序相反)并执行。
如果线程有任何线程特定的数据(TLS),则在执行清理处理程序后,
会以未指定的顺序调用相应的析构函数。
当线程终止时,进程共享资源(例如互斥锁、条件变量、信号量和文件描述符)不会被释放,
并且不会调用使用 atexit() 注册的函数。
在进程中的最后一个线程终止后,该进程通过调用 exit() 以退出状态为零来终止; 因此,进程共享资源被释放时,使用 atexit() 注册的函数会被调用。
从 main 以外的任何线程的 start 函数执行返回会导致对 pthread_exit() 的隐式调用,
使用函数的返回值作为线程的退出状态。
为了允许其他线程继续执行,main 应通过调用 pthread_exit() 而不是 exit() 来终止。
retval 指向的值不应位于调用线程的栈上,因为该栈的内容在线程终止后未定义。
当前,在具有死线程组领导者(a dead thread group leader)的
已停止线程组(a stopped thread group)上 waiting 的内核实现逻辑存在限制。 如果将停止信号发送到线程组领导者已调用 pthread_exit() 的前台进程,
则这可能表现为终端锁定等问题。
正确使用方式
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
pthread_t tid[2];
int cnt = 1;
void *recv_thread(void *arg)
{
int mtp = 2;
char *poll = (char *)malloc(10);
memset(tmp, 0, sizeof(10));
tmp[0] = 'h';
tmp[1] = 'e';
tmp[2] = 'l';
tmp[3] = 'l';
tmp[4] = 'o';
printf("recv thread, enter ...\n");
while(cnt) {
poll++;
}
printf("recv thread, exit ...\n");
pthread_exit((void *)tmp);
}
void *send_thread(void *arg) {
sleep(5);
cnt = 0;
pthread_exit(NULL);
}
int main(int argc, const char *argv[]) {
int ret;
void *cur = NULL;
printf("main thread, enter ...\n");
ret = pthread_create(&tid[0], NULL, recv_thread, NULL);
if(ret != 0) {
printf("main thread fail to pthread_create recv_thread, %s\n",strerror(errno));
exit(EXIT_FAILURE);
}
printf("main thread, pthread_create recv_thread successful\n");
ret = pthread_create(&tid[1], NULL, send_thread, NULL);
if(ret != 0) {
printf("main thread, fail to pthread_create send_thread, %s\n",strerror(errno));
exit(EXIT_FAILURE);
}
printf("main thread, pthread_create send_thread successful\n");
printf("main thread, before join\n");
pthread_join(tid[0],&cur);
printf("%s\n", (char *)cur);
if (cur) {
free(cur);
cur = NULL;
}
pthread_join(tid[1],NULL);
printf("main thread, after join\n");
exit(EXIT_SUCCESS);
}