Day 55 Linux 线程控制语句pthread_exit pthread_join pthread_detach pthread_cancel 线程属性

article/2025/9/19 8:43:47

目录

1. 线程控制语句

1.1pthread_exit函数

1.2pthread_join函数

1.3pthread_detach函数

1.4pthread_cancel函数

控制原语对比

2. 线程属性

2.1线程属性初始化

2.2线程的分离状态

 2.3线程使用注意事项


1. 线程控制语句

1.1pthread_exit函数

将单个当前线程退出
void pthread_exit ( void * retval )
参数:
        retval表示线程退出状态,通常传 NULL
结论: 线程中,禁止使用exit 函数,会导致进程内所有线程全部退出
在不添加 sleep 控制输出顺序的情况下。 pthread_create 在循环中,几乎瞬间创建 5 个线程,但只有第 1个线程有机会输出( 或者第 2 个也有,也可能没有,取决于内核调度 ) 如果第 3 个线程执行了 exit ,将整个进程退出了,所以全部线程退出了
所以,多线程环境中,应尽量少用,或者不使用exit 函数,取而代之使用 pthread_exit 函数,将单个线程退出。任何线程里 exit 导致进程退出 ,其他线程未工作结束,主控线程退出时不能 return exit
另注意, pthread_exit 或者 return 返回的指针所指向的内存单元必须是全局的或者是用 malloc 分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

 

编写多线程程序,总結 exit return pthread_exit 各自退出效果。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <pthread.h>void sys_err(const char *str)
{perror(str);exit(1);
}void fun(void)
{//return NULL;pthread_exit(NULL);//表示退出当前线程
}void *tfn(void *arg)
{int i=(int)arg;printf("--I am %dth thread pid=%d, tid=%lu\n",i+1,getpid(),pthread_self());if(i==2){// exit(0);//表示退出进程//return NULL;//表示返回到函数调用者那里去fun();}
}int main()
{int i;int ret;pthread_t tid;for(i=0;i<5;i++){ret=pthread_create(&tid,NULL,tfn,(void *)i);if(ret!=0){sys_err("pthread_create() err");}sleep(1);}sleep(i);printf("pthread_create finish!\n");return 0;
}
return: 返回到调用者那里去
pthread_exit(): 将调用该函数的线程退出
exit: 将进程退出

1.2pthread_join函数

阻塞等待线程退出,获取线程退出状态,其作用对应进程中wait()函数

int pthread_join ( pthread_t thread , void ** retval );
返回值:
        成功 : 0 ;
        失败 : 错误号。
参数 1 : 传入 thread : 线程 ID ([ 注意 ] 不是指针 );
参数 2 retval : 存储线程结束状态。
对比记忆
进程中:
        main返回值、 exit 参数 -->int ;等待子进程结束 wait 函数参数 - ->int *
线程中:
        线程主函数返回值、pthread_exit->void*; 等待线程结束 pthread_join 函数参数 - ->void **

参数retval 非空用法

例程:子线程完成一个变量空间开辟,并返回初始化结果

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<pthread.h>void sys_err(const char *str)
{perror(str);exit(1);
}void  *tfn(void *arg)
{int *pval;pval=malloc(sizeof(int));*pval=10000; return (void *)pval;
}int main()
{pthread_t tid;int *retval;int ret=pthread_create(&tid,NULL,tfn,NULL);if(ret!=0){sys_err("pthread_create err");}ret =pthread_join(tid,(void **)&retval);if(ret!=0)sys_err("pthread_join err");printf("child thread exit with ret =%d,val=%d\n",ret,*retval);pthread_exit(NULL);free(retval); retval =NULL;return 0;
}

 例程:子线程完成一个字符串空间开辟,并返回初始化结果hello

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <pthread.h>void sys_err(const char *str)
{perror(str);exit(1);
}void *tfn(void *arg)
{char *pval;pval=malloc(sizeof(char)*10);memset(pval,0,10);strcpy(pval,"hello");pthread_exit(100);
}int main()
{pthread_t tid;char *retval;int ret=pthread_create(&tid,NULL,tfn,NULL);if(ret != 0){sys_err("pthread_create err");}
//pthread_join等待回收子线程
//参1:tid 需要回收的线程id
//参2:需要回收的线程的返回值(是个地址)
//返回值:成功0,失败返回错误码ret= pthread_join(tid,(void **)&retval);if(ret != 0)sys_err("pthread_join err");printf("child thread exit with val=%d\n",retval);while(1); //其他业务功能
// pthread_exit(NULL);return 0;
}
调用该函数的线程将挂起等待,直到 id thread 的线程终止 . thread 线程以不同的方法终止 , 通过
pthread_ join 得到的终止状态是不同的
总结如下 :
1. 如果 thread 线程通过 return 返回, retval 所指向的单元里存放的是 thread 线程函数的返回值
2. 如果 thread 线程是自己调用 pthread_exit 终止的, retval 所指向的单元存放的是传给 pthread_ exit 的参数。
3. 如果 thread 线程被别的线程调用 pthread_cancel. 异常终止掉, retval 所指向的单元里存放的是常数PTHREAD_CANCELED
4. 如果对 thread 线程的终止状态不感兴趣,可以传 NULL retval 参数

1.3pthread_detach函数

实现线程分离

 int pthread_detach(pthread_t thread);

返回值:
        成功 : 0 ;
        失败 : 错误号
参数: thread 需要分离的线程 id
线程分离状态:指定该状态,线程主动与主控线程断开关系。线程结束后,其退出状态不由其他线程获取,而直接自己自动释放。网络、多线程服务器常用。
进程若有该机制,将不会产生僵尸进程。僵尸进程的产生主要由于进程死后,大部分资源被释放,一点残留资源仍存于系统中,导致内核认为该进程仍存在。
也可使用pthread_create 函数参 2( 线程属性 ) 来设置线程分离,后面会讲到。
例程:使用 pthread_detach 函数实现线程分离
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>void *tfn(void *arg)
{printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());return NULL ;
}int main(void)
{pthread_t tid;int ret = pthread_create(&tid, NULL, tfn, NULL);if(ret!=0){perror("pthread_create error");exit(1);}ret = pthread_detach(tid);//设置线程分离,线程终止、会自动清理pcb,无需回收if(ret!=0){perror("pthread_detach error");}ret=pthread_join(tid, NULL);printf("thread_join() ret = %d\n",ret);if(ret!=0){perror("thread_join error");}printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());return 0;
}
一般情况下,线程终止后,其终止状态一直保留到其它线程调用 pthread_join 获取它的状态为止。但是线程也可以被置为detach 状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。
不能对一个已经处于 detach 状态的线程调用 pthread_join, 这样的调用将返回错误。也就是说,如果已经对一个线程调用了pthread_detach 就不能再调用 pthread_join 了。

1.4pthread_cancel函数

杀死 ( 取消 ) 一个线程,需要取消点  [ 其作用对应进程中 kill() 函数 ]
int pthread_cancel ( pthread_t thread );
返回值:
        成功:0 ;
        失败:错误号。
参数: thread 待杀死的线程 id
[注意]
线程的取消并不是实时的,而有一定的延时。需要等待线程到达某个取消点 ( 检查点 )
取消点,是线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统调用 creat, open,pause,close, read, write.... 执行命令 man 7 pthreads 可以查看具备这些取消点的系统调用列表。
可粗略认为一个系统调用 ( 进入内核 ) 即为一个取消点。如线程中没有取消点,可以通过调用 ==pthread_testcancel()==函数自行设置一个取消点。
例程:终止线程的三种方法。注意 取消点 的概念。

 

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>void *tfn1(void *arg)
{printf("thread 1 returning\n");return (void *)111;
}void *tfn2(void *arg)
{printf("thread 2 exiting\n");pthread_exit((void *)222);
}void *tfn3(void *arg)
{while (1){printf("thread 3: I'm going to die in 3 seconds ...\n");sleep(1);//pthread_testcancel(); //自己添加取消点*/}return (void *)888;
}int main(void)
{pthread_t tid1;pthread_t tid2;pthread_t tid3;void *tret = NULL;pthread_create(&tid1, NULL, tfn1, NULL);pthread_join(tid1, &tret);printf("thread 1 exit code = %ld\n\n", (long int)tret);pthread_create(&tid2, NULL, tfn2, NULL);pthread_join(tid2, &tret);printf("thread 2 exit code = %ld\n\n", (long int)tret);pthread_create(&tid3, NULL, tfn3, NULL);sleep(3);pthread_cancel(tid3);pthread_join(tid3, &tret);printf("thread 3 exit code = %ld\n", (long int)tret);return 0;
}
测试,注释 tfn3() 函数中, printf(),sleep() 等函数功能后,发现无法杀死,说明只有进入内核,才能杀死

控制原语对比

2. 线程属性

linux 下线程的属性是可以根据实际项目需要,进行设置,之前我们讨论的线程都是采用线程的默认属性,默认属性已经可以解决绝大多数开发时遇到的问题。如我们对程序的性能提出 更高的要求那么需要设置线程属性,比如可以通过设置线程的大小来降低内存的使用,增加最大线程个数。
typedef struct
{
        int detachstate ; // 线程的分离状态。
        int schedpolicy ; // 线程调度策略。
        struct sched_param schedparam ; // 线程的调度参数。
        int inhertsched ; // 线程的继承性。
        int scope ; // 线程的作用域。
        size_t guardsize ; // 线程栈末尾的警戒缓冲区大小
        int stackaddr_set ; // 线程栈的设置,
        void * stackaddr ; // 线程栈的位置。
        size_t stacksize ; // 线程栈的大小。
} pthread_attr_t ;

 主要结构体成员:

        1.线程分离状态。
        2.线程栈大小 ( 默认平均分配 )
        3.线程栈警戒缓冲区大小 ( 位于栈末尾 )
属性值不能直接设置,须使用相关函数进行操作,初始化的函数为 pthread_ attr_init , 这个函数必须在pthread_create 函数之前调用。之后须用 pthread_attr_destroy 函数来释放资源。

 

线程属性主要包括如下属性。作用域 (scope) 、栈尺寸 (stack size) 、栈地址 (stack adress) 、优先级
(priority) 、分离的状态 (detached state) 、调度策略和参数 (scheduling policy and parameters) 。默认的属性为非绑定、非分离、缺省的堆栈、与父进程同样级别的优先级。

2.1线程属性初始化

注意 : 应先初始化线程属性,再 pthread_ create 创建线程。
初始化线程属性,将线程的属性初始化为默认值,分配一些存储空间。
int pthread_attr_init ( pthread_attr_t * attr );
成功 : 0 ; 失败 : 错误号。

销毁线程属性所占用的资源,删除初始化期间分配的存储空间

int pthread_attr_destroy ( pthread_attr_t * attr );
成功 : 0 ; 失败 : 错误号。

2.2线程的分离状态

线程的分离状态决定一个线程以什么样的方式来终止自己。
非分离状态线程的默认属性是非分离状态 , 这种情况下 , 原有的线程等待创建的线程结束。只有当
pthread_join() 函数返回时,创建的新线程才算终止 , 才能释放自己占用的系统资源。
分离状态 : 分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。应该根据自己的需要,选择适当的分离状态。
线程分离状态的函数。

 设置线程属性,分离or非分离

int pthread_attr_setdetachstate ( pthread_attr_t * attr , int detachstate );

获取线程属性,分离or非分离 

int pthread_attr_getdetachstate ( pthread_attr_t * attr , int * detachstate );
参数 :
attr : 已初始化的线程属性。
        detachstate : PTHREAD_CREATE_DETACHED ( 分离线程 )
                                PTHREAD_CREATE_JOINABLE ( 非分离线程 )

例程:通过设置线程分离属性,创建分离子线程

 

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>void *tfn(void *arg)
{printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());sleep(5);return NULL ;
}int main(void)
{pthread_t tid;int *retval;pthread_attr_t attr;int ret=pthread_attr_init(&attr);if(ret!=0){fprintf(stderr,"attr_init error:%s\n",strerror(ret));exit(1);}ret=pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);//设置线程属性为,分离属性if(ret!=0){fprintf(stderr, "attr_setdetachstate error:%s\n",strerror(ret));exit(1);}ret = pthread_create(&tid, &attr, tfn, NULL);if(ret!=0){perror("pthread_create error");exit(1);}ret = pthread_attr_destroy(&attr);if(ret!=0){fprintf(stderr, "attr_ destroy error:%s\n",strerror(ret));exit(1);}printf("111:tid=%lu\n",tid);ret= pthread_join(tid,(void **)&retval);printf("222:tid=%lu\n",tid);printf("thread_join() ret = %d\n",ret);if(ret!=0){fprintf(stderr, "thread_join error:%s\n",strerror(ret));exit(1);}printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());return 0;
}
注意
如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create 的线程就得到了错误的线程号。要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用pthread_cond_timedwait 函数,让这个线程等待一会儿, 留出足够的时间让函数pthread_create 返回。设置一段等待时间,是在多线程编程里常用的方法。但是注意不要使用诸如wait( )之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。
小结:
设置分离属性 :
  • pthread_attr_t attr 创建一个线程属性结构体变量
  • pthread_attr_init(&attr); 初始化线程属性
  • pthread_attr_ setdetachstate(&attrPTHREAD_CREATE_ DETACHED); 设置线程属性为分离态
  • pthread_create(&tid, &attr, tfn, NULL); 借助修改后的设置线程属性创建为分离态的新线程
  • pthread_attr_destroy(&attr); 销毁线程属性

 

 2.3线程使用注意事项

1. 主线程退出其他线程不退出,主线程应调用 pthread_exit == 任何线程调用 exit 都会导致整个线程组退出==
2. 避免僵尸线程
        pthread_join 回收
        pthread_detache 分离
        pthread_create 指定分离属性。
        被join 的线程可能在 join 函数返回前就释放完自己的所有内存资源,所以不应当返回被回收线程栈中的值;
3.malloc mmap 申请的内存可以被其他线程释放。
4. 应避免在多线程模型中调用 fork ,除非马上 exec, 子进程中只有调用 fork 的线程存在,其他线程在子进程中均pthread_exit.
5. 信号的复杂语义很难和多线程共存,应避免在多线程引入信号机制。


http://chatgpt.dhexx.cn/article/mfHOCRAb.shtml

相关文章

pthread 线程基本函数

文章目录 一、int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);二、int pthread_join(pthread_t tid, void **thread_return);三、int pthread_detach(pthread_t tid);四、void pthread_exit(void *retval);五、int …

pthread

POSIX线程&#xff08;POSIX threads&#xff09;&#xff0c;简称Pthreads&#xff0c;是线程的POSIX标准。该标准定义了创建和操纵线程的一整套API。在类Unix操作系统&#xff08;Unix、Linux、Mac OS X等&#xff09;中&#xff0c;都使用Pthreads作为操作系统的线程。 1、p…

线程以及pthread库的使用

一.什么是线程 你可以想象你一边听歌一边打游戏&#xff0c;如果是操作系统会怎么做呢&#xff1f;先执行 ListenMusic 再执行 PlayGame&#xff0c;还是先执行 PlayGame 再执行 ListenMusic 呢&#xff1f;好像都不太合适。为了实现这个目的&#xff0c;就需要引入线程这个概念…

多线程02---pThread简介

1.简介 pthread 是属于 POSIX 多线程开发框架。它是c语言提供的一个跨平台的多线程解决方案。由于其在iOS编程中&#xff0c;操作比较麻烦&#xff0c;一般不用&#xff0c;这里介绍仅仅作为了解。 2.pthread的使用 通过以下函数创建pthread&#xff0c;在C语言中类型的结尾…

Qt 无法识别的外部符号.无法解析的外部符号

原因: 很多博客都说了这个原因,是因为后续在自己的类中,引入Q_OBJECT , 导致vs无法自动生成 moc_XXX.cpp类似的文件, 编译时候,找不到导致的(符号链接). 他人解决办法: 看了很多博客,说用moc_xx.exe, 重新生成对应的.h头文件,一下,就可以了;有的建议重新把类添加一下,然后清…

Qt项目 无法解析的外部符号_WinMainCRTStartup

1、无法解析的外部符号_WinMainCRTStartup 在编译Qt项目的时候突然说找不到_WinMainCRTStartup函数&#xff0c;_WinMainCRTStartup是Qt的主函数。找不到可能是main函数不在工程中。 选中main.cpp点击编译 点击移除再重新添加

QT无法解析的外部符号问题

moc_widget.obj:-1: error: LNK2019: 无法解析的外部符号 "private: void __thiscall Widget::on_pushButton_6_clicked(void)" (?on_pushButton_6_clickedWidgetAAEXXZ)&#xff0c;该符号在函数 "private: static void __cdecl Widget::qt_static_metacall(c…

QT疑难解决:无法解析的外部符号

无法解析的外部符号 _imp_XXXXXXXXX 出现字符_imp&#xff0c;说明不是真正的静态库&#xff0c;而是某个动态库的导入库&#xff0c;导入函数和自己不同名&#xff0c;所以加了字符_imp。 引入相应库 打开MSDN搜索函数xxxxx&#xff1a;http://msdn.microsoft.com/en-us/dn…

CUDA编程时遇到无法解析外部符号threadIdx或blockIdx问题的解决办法

在CUDA编程时遇到出现无法解析外部符号threadIdx或blockIdx问题的解决办法 在CUDA编程之前要确保工程项目的配置属性一致性如图一所示。然后点击项目—>属性确保配置属性无误。 VC 目录 可执行文件目录&#xff1a;…\NVIDIA GPU Computing Toolkit\CUDA\v10.2\bin 包含目…

LNK2019 无法解析的外部符号

LNK 2019 遇到了很多次这个错误&#xff0c;会持续更新遇到该错误产生的原因和解决方法 这个错误是由于我们调用的函数所定义的某个文件在编译时没有正确链接导致的&#xff0c;错误显示如下&#xff1a; 然后根据错误提示找到该函数 ImGui_ImplOpenGL3_Shutdown();ImGui_Impl…

无法解析的外部符号解决方法

步骤1&#xff1a; 按无法解析的顺序来解决问题 比如有两个无法解析&#xff0c;先解决第一个。 步骤2&#xff1a; 点击LNK2001,会跳转到网页&#xff0c;网页会提示哪些无法解析的问题是由哪个无法解析的错误引起的&#xff0c;这样可以减少解决无法解析的错误。 步骤3. …

无法解析的外部符号问题小结

问题1&#xff1a;在编写通信相关程序中&#xff0c;引用了一个静态库&#xff08;该静态库编译没有问题&#xff0c;并被其他项目引用&#xff09;&#xff0c;该库是对SOCKET的一个封装。基本结构如下&#xff1a; 在属性中添加了该库的引用后&#xff0c;编译仍然报错&#…

无法解析的外部符号main

今天在写程序的时候遇到个问题&#xff1a; 然后就去看了相应的解决方法 发现都不管用&#xff1a; 1.不是文件名.c或.cpp的问题 2.不是没有包含相应头文件的问题 3.不是写的控制台程序而使用的Windows连接程序&#xff08;Winmain&#xff09; 最后发现是因为在刚开始打开项…

无法解析的外部符号xxx 该符号在函数xxx中被引用

无法解析的外部符号xxx 该符号在函数xxx中被引用 更多相关错误可查看&#xff1a;https://blog.csdn.net/weixin_44120025/article/details/115279563 下面主要讲述一个解决方案中包含多个项目且它们之间互相引用的情况。 在一个解决方案已经有多个项目的情况下创建一个项目&am…

Visual Studio 2022配置GAMP出现 LNK2019无法解析外部符号_imp_timeGetTime@0

#vs2022配置GAMP 使用visual studio 2022配置软件GAMP的过程可以参考vs2019配置GAMP的详细过程。 在vs2022按照vs2019配置过程后&#xff0c;生成解决方案时&#xff0c;出现了LNK2019 无法解析的外部符号 _imp_timeGetTim0, 函数_tickget中引用了该符号。 解决方法如下&#…

跟着LearnOpenGLCN学习OpenGL配置GLAD环境时出现无法解析外部符号问题的解决

根据LearnOpenGLCN里的步骤&#xff1a; 1.将解压下来的glad和KHR直接复制到glfw-3.3.4.bin.WIN32的include里。 2.glad.c放到工程文件中去。 3.GLFW和GLAD配置环境 第一步&#xff1a; 第二步&#xff1a; 在配置环境后&#xff0c;运行调用窗口代码&#xff0c;出现以下错…

VS配置PCL“无法解析外部符号”

一开始报错&#xff1a; 一般原因是没有包括需要的 .lib 报错说明可能出现在vtk 和pcl_visualization 的lib上。在依赖库中添加pcl_visualization.lib 或者在.cmake文件中添加visualization重新编译&#xff0c;如下&#xff1a; 之后&#xff0c;报错只有两条&#xff1a; …

C++ error LNK2019无法解析外部符号

背景&#xff1a;用visual studio 2019开发MFC项目&#xff0c;第一次写c的项目&#xff0c;之前都是写c#和go的项目。 自定义一个类&#xff0c;引用自定义的类&#xff0c;具体的是引用.h文件&#xff0c;死活不成功&#xff0c;一直报error LNK2019无法解析外部符号这个错误…

VS在引用外库运行时显示无法解析外部符号

VS引用外库在运行时显示无法解析外部符号 问题截图 原因 链接器无法链接到 .lib&#xff0c;可以查看下图位置看库是否引入成功。 解决 检查库的配置是否正确。下面列出我在引用第三方库时的配置过程和注意事项。 使用cmake生成、vs编译生成的库&#xff0c;需要添加三个…

UE4使用UUserWidget无法解析外部符号

如图&#xff0c;明明vs代码没报错&#xff0c;但是编译的时候&#xff0c;总是提示&#xff1a; 那就说明&#xff0c;你的项目没有引入UMG模块&#xff1a; 找到这个文件&#xff0c;然后&#xff0c;在后面添加&#xff1a; 模块的介绍&#xff0c;网上一大堆&#xff0c;比…