目录
1 线程基础
2 Linux线程库
2.1 线程创建 – pthread_create
2.2 线程结束 – pthread_exit
2.3 线程查看tid函数
2.4 线程间参数传递(重点)
2.4.1 练习
2.5 线程查看命令(多线程)
2.6 线程回收 – pthread_join
2.7 线程分离pthead_detach和设置属性
2.7.1 pthead_detach方式
2.7.1 设置Attr属性
2.8 取消一个线程
2.8.1 pthread_cancel
2.8.2 pthread_testcancel
2.8.3 pthread_setcancelstate
2.8.4 pthread_setcanceltype
2.9 线程的清理
掌握:线程概念、线程特点、线程创建
线程概念
进程
线程
线程特点
使用多线程的好处
线程共享资源
一个进程中的多个线程共享以下资源:
线程私有资源
每个线程私有的资源包括:
linux内核中并没有实现线程,所以通过线程库实现
pthread线程库中提供了如下基本操作
同步和互斥机制
#include
int pthread_create(pthread_t *thread, const
pthread_attr_t *attr, void *(*routine)(void *), void *arg);
示例:
#include
#include
#include
int *testThread(char *arg){
printf("This is a thread test\n");
return NULL;
}
int main(){
pthread_t tid;
int ret;
ret = pthread_create(&tid,NULL,(void *)testThread,NULL);
printf("This is main thread\n");
sleep(1);
}
//如果不加printf和sleep1,主线程结束,子线程也结束可能就打印不出内容。
linux@linux:~/Desktop$ gcc -o pthread pthread.c -lpthread
linux@linux:~/Desktop$ ./pthread
linux@linux:~/Desktop$ ./pthread
编译错误分析:
createP_t.c:14:36: warning: passing argument 3 of ‘pthread_create’ from incompatible pointer type [-Wincompatible-pointer-types]
ret = pthread_create(&tid,NULL,testThread,NULL);
^
In file included from createP_t.c:1:0:
/usr/include/pthread.h:233:12: note: expected ‘void * (*)(void *)’ but argument is of type ‘int * (*)(char *)’
意义:表示pthread_create参数3的定义和实际代码不符合,期望的是void * (*)(void *) ,实际的代码是int * (*)(char *)
解决方法:改为pthread_create(&tid,NULL,(void*)testThread,NULL);
createP_t.c:(.text+0x4b):对‘pthread_create’未定义的引用
collect2: error: ld returned 1 exit status --------这个链接错误,
表示pthread_create这个函数没有实现
解决方法:编译时候加 -lpthread
注意事项:1. 主进程的退出,它创建的线程也会退出。
线程创建需要时间,如果主进程马上退出,那线程不能得到执行
#include
void pthread_exit(void *retval);
示例
#include
#include
#include
void *testThread(void *arg){
printf("This is a thread test\n");
pthread_exit(NULL);
printf("after pthread exit\n"); //不会被打印,线程已经清理
}
int main(){
pthread_t tid;
int ret;
int arg = 5;
ret = pthread_create(&tid,NULL,testThread,(void *)arg);
printf("This is main thread\n");
sleep(1);
}
//运行结果
linux@linux:~/Desktop$ gcc -o pthread pthread.c -lpthread
linux@linux:~/Desktop$ ./pthread
This is main thread
This is a thread test
pthread_t pthread_self(void) 查看自己的TID
#include
pthread_t pthread_self(void);
示例:
#include
#include
#include
void *testThread(void *arg){
printf("This is a thread test,pid=%d,tid=%lu\n",getpid(),pthread_self());
// return NULL;
pthread_exit(NULL);
printf("after pthread exit\n");
}
int main(){
pthread_t tid;
int ret;
ret = pthread_create(&tid,NULL,testThread,(void *)arg);
printf("This is main thread,tid=%lu\n",tid);
sleep(1);
}
pthread_create(pthread_t *thread, const
pthread_attr_t *attr, void *(*routine)(void *), void *arg);
最后一个参数
示例:
//方式1
//直接传参数值也可以,把数当地址来传入了。因为int和指针都是4字节,如果是long就不行了
#include
#include
#include
void *testThread(void *arg){
printf("This is a thread test,pid=%d,tid=%lu\n",getpid(),pthread_self());
// return NULL;
printf("input arg=%d\n",(int)arg);
pthread_exit(NULL);
printf("after pthread exit\n");
}
int main(){
pthread_t tid;
int ret;
int arg = 5;
ret = pthread_create(&tid,NULL,testThread,(void *)arg);
printf("This is main thread,tid=%lu\n",tid);
sleep(1);
}
//方式2
#include
#include
#include
void *testThread(void *arg){
printf("This is a thread test,pid=%d,tid=%lu\n",getpid(),pthread_self());
// return NULL;
printf("input arg=%d\n",*(int *)arg);
pthread_exit(NULL);
printf("after pthread exit\n");
}
int main(){
pthread_t tid;
int ret;
int arg = 5;
ret = pthread_create(&tid,NULL,testThread,(void *)&arg);
printf("This is main thread,tid=%lu\n",tid);
sleep(1);
}
补充:
编译错误:
createP_t.c:8:34: warning: dereferencing ‘void *’ pointer
printf("input arg=%d\n",(int)*arg);
^
createP_t.c:8:5: error: invalid use of void expression
printf("input arg=%d\n",(int)*arg);
错误原因是void *类型指针不能直接用*取值(*arg),因为编译不知道数据类型。
解决方法:转换为指定的指针类型后再用*取值 比如:*(int *)arg
示例:
#include
#include
#include
void *testThread(void *arg){
printf("This is a thread test,pid=%d,tid=%lu\n",getpid(),pthread_self());
// return NULL;
printf("This is %d thread.\n", (int)arg);
// pthread_exit(NULL);
while(1){
sleep(1);
}
printf("after pthread exit\n");
}
int main(){
pthread_t tid[5];
int ret;
int arg = 5;
int i;
for(i=0;i<5;i++){
ret = pthread_create(&tid[i],NULL,testThread,(void *)i);
// sleep(1); //执行效率低无法打印i的值,因为传入的是地址,还没来得及改变,可以改用传值
printf("This is main thread,tid=%lu\n",tid[i]);
}
while(1){
sleep(1);
}
}
运行错误:
*** stack smashing detected ***: ./mthread_t terminated
已放弃 (核心已转储)
原因:栈被破坏了(数组越界)
使用pthread_create实现 10 个子线程,并且让每个子线程打印自己的线程号
#include
#include
#include
int *Thread_fun(char *arg)
{
printf("This is a thread test.pid=%d,tid=%lu,\n",getpid(),pthread_self());
printf("This is %d thread.\n",(int)arg);
while(1)
{
sleep(1);
}
printf("after pthread exit\n");
return 0;
}
int main(int argc, char * argv[])
{
int i;
pthread_t tid[10];
int ret = 0;
for(i = 0; i < 10; i++)
{
ret = pthread_create(&tid[i],NULL,(void *)Thread_fun,(void *)i);
printf("This i main thread,tid=%lu\n",tid[i]);
}
while(1)
{
sleep(1);
}
}
ps -eLf
#include
int pthread_join(pthread_t thread, void **retval);
对于一个默认属性的线程 A 来说,线程占用的资源并不会因为执行结束而得到释放
注意:pthread_join 是阻塞函数,如果回收的线程没有结束,则一直等待
示例
#include
#include
#include
void *Thread_fun(void *arg)
{
printf("This is a child thread\n");
sleep(1);
pthread_exit("thread return");
}
int main(int argc, char * argv[])
{
pthread_t tid;
void *retv;
pthread_create(&tid,NULL,Thread_fun,NULL);
pthread_join(tid,&retv);
printf("thread ret=%s\n",(char *)retv);
sleep(1);
}
//运行结果
linux@linux:~/Desktop$ ./test_pthread
This is a child thread
thread ret=thread return
多个线程回收
#include
#include
#include
void *func(void *arg){
printf("This is child thread\n");
sleep(25);
pthread_exit("thread return");
}
int main(){
pthread_t tid[100];
void *retv;
int i;
for(i=0;i<100;i++){
pthread_create(&tid[i],NULL,func,NULL);
}
for(i=0;i<100;i++){
pthread_join(tid[i],&retv);
printf("thread ret=%s\n",(char*)retv);
}
while(1){
sleep(1);
}
}
回收效果,使用top命令
linux@linux:~$ ps -ef|grep "pjoin"
linux 16330 2072 0 10:46 ? 00:00:01 gedit /home/linux/Desktop/pjoin.c
linux 16467 2732 0 10:50 pts/9 00:00:00 ./pjoin
linux 16573 15358 0 10:51 pts/1 00:00:00 grep --color=auto pjoin
linux@linux:~$ top -p 16467
发现回收前后,虚拟内存减小,实际内存减小,如果不使用回收,内存不会有变化还可能变大。
使用线程的分离两种方式:
1 使用pthread_detach
2 创建线程时候设置为分离属性
int pthread_detach(pthread_t thread);
成功:0;失败:错误号
指定该状态,线程主动与主控线程断开关系。线程结束后(不会产生僵尸线程)
此种方式不需要通过主线程取回收线程资源。
示例
#include
#include
#include
void *func(void *arg){
pthread_detach(pthread_self()); //方式2
printf("This is child thread\n");
sleep(25);
pthread_exit("thread return");
}
int main(){
pthread_t tid[100];
void *retv;
int i;
for(i=0;i<100;i++){
pthread_create(&tid[i],NULL,func,NULL);
//pthread_detach(tid); //方式1
}
while(1){
sleep(1);
}
}
实际效果:同2.6回收效果,虚拟内存减小,实际内存减小,如果不使用detach,内存不会有变化还可能变大。
pthread_attr_t attr; /*通过线程属性来设置游离态(分离态)*/
设置线程属性为分离
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
示例:
#include
#include
#include
void *func(void *arg){
printf("This is child thread\n");
sleep(25);
pthread_exit("thread return");
}
int main(){
pthread_t tid[100];
void *retv;
int i;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED); //分离属性
for(i=0;i<100;i++){
pthread_create(&tid[i],&attr,func,NULL);
// pthread_detach(tid);
}
while(1){
sleep(1);
}
}
线程分离的目的:
资源回收:当线程被标记为可分离(detached)时,线程退出后,系统会自动回收其占用的资源,无需其他线程等待或执行特定的回收操作。这对于长时间运行或创建大量线程的应用程序很有用,因为它可以减少资源泄漏的风险。
线程管理:标记线程为可分离使得其独立于主线程或其他线程而存在,它可以自主地运行和结束,不会影响其他线程的正常执行。这对于一些需要并发执行的任务或周期性任务非常有用,可以提高整体的系统性能和响应能力。
需要注意的是,线程创建时的默认属性是非分离(joinable),即需要使用 pthread_join
函数来等待线程的结束并回收资源。如果需要将线程设置为可分离属性,可以使用 pthread_attr_setdetachstate
函数将属性设置为 PTHREAD_CREATE_DETACHED
。
int pthread_cancel(pthread_t thread); 杀死一个线程
作用:如果一个线程是个死循环,那么exit可能永远也执行不到。这种情况需要取消线程的功能。
意义:随时杀掉一个线程,如果使用kill命令,会把进程也一起杀掉。
注意:线程的取消要有取消点才可以,不是说取消就取消,线程的取消点主要是阻塞的系统调用
示例:
#include
#include
#include
void *func(void *arg){
printf("This is child thread\n");
while(1)
{
sleep(5);
}
pthread_exit("thread return"); //永远不会执行
}
int main(){
pthread_t tid;
void *retv;
int i;
pthread_create(&tid,NULL,func,NULL);
sleep(5);
pthread_cancel(tid);
pthread_join(tid,&retv);
printf("thread ret=%s\n",(char*)retv); //这里会出现段错误,这是空指针
while(1){
sleep(1);
}
}
运行段错误调试:
可以使用gdb调试
使用gdb 运行代码,gdb ./youapp
(gdb) run
等待出现Thread 1 "pcancel" received signal SIGSEGV, Segmentation fault.
输入命令bt(打印调用栈)
(gdb) bt
#0 0x00007ffff783ecd0 in vfprintf () from /lib/x86_64-linux-gnu/libc.so.6 //vfprintf报错
#1 0x00007ffff78458a9 in printf () from /lib/x86_64-linux-gnu/libc.so.6 //#2调用了#1
#2 0x00000000004007f9 in main () at pcancel.c:21 //栈底
确定段错误位置是pcancel.c 21行
注释掉后调试结果
linux@linux:~$ ps -eLf|grep "pcancel"
linux 3467 2962 3467 0 2 02:50 pts/7 00:00:00 ./pcancel
linux 3467 2962 3468 0 2 02:50 pts/7 00:00:00 ./pcancel
linux 3470 3184 3470 0 1 02:50 pts/2 00:00:00 grep --color=auto pcancel
linux@linux:~$ ps -eLf|grep "pcancel"
linux 3467 2962 3467 0 2 02:50 pts/7 00:00:00 ./pcancel
linux 3467 2962 3468 0 2 02:50 pts/7 00:00:00 ./pcancel
linux 3472 3184 3472 0 1 02:50 pts/2 00:00:00 grep --color=auto pcancel
linux@linux:~$ ps -eLf|grep "pcancel"
linux 3467 2962 3467 0 1 02:50 pts/7 00:00:00 ./pcancel
linux 3474 3184 3474 0 1 02:50 pts/2 00:00:00 grep --color=auto pcancel
linux@linux:~$
如果没有取消点,手动设置一个
void pthread_testcancel(void);
#include
#include
#include
void *func(void *arg){
printf("This is child thread\n");
while(1)
{
pthread_testcancel(); //如果程序代码很长,找不到死循环的位置,可以用这段代码
}
pthread_exit("thread return"); //永远不会执行
}
int main(){
pthread_t tid;
void *retv;
int i;
pthread_create(&tid,NULL,func,NULL);
sleep(5);
pthread_cancel(tid);
while(1){
sleep(1);
}
}
如果没有取消点,手动设置一个
目的:让有些代码可以被取消,有些代码不能被取消。有些先后顺序不好找的时候。
int pthread_setcancelstate(int state, int *oldstate);
PTHREAD_CANCEL_ENABLE
PTHREAD_CANCEL_DISABLE
#include
#include
#include
void *func(void *arg){
printf("This is child thread\n");
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL); //不能被取消,不管有没有阻塞点
while(1)
{
sleep(5);
pthread_testcancel();
}
pthread_exit("thread return"); //永远不会执行
}
int main(){
pthread_t tid;
void *retv;
int i;
pthread_create(&tid,NULL,func,NULL);
sleep(1);
pthread_cancel(tid);
pthread_join(tid,&retv);
// printf("thread ret=%s\n",(char*)retv);
while(1){
sleep(1);
}
}
//运行结果
linux@linux:~$ ps -eLf|grep "pcancel"
linux 6271 2962 6271 0 2 03:43 pts/7 00:00:00 ./pcancel
linux 6271 2962 6272 0 2 03:43 pts/7 00:00:00 ./pcancel
linux 6287 3184 6287 0 1 03:46 pts/2 00:00:00 grep --color=auto pcancel
此时在设置可以取消。
#include
#include
#include
void *func(void *arg){
printf("This is child thread\n");
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
sleep(5);
pthread_testcancel(); //如果程序代码很长,找不到死循环的位置,可以用这段代码
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL); //可以取消了
pthread_exit("thread return"); //永远不会执行
}
int main(){
pthread_t tid;
void *retv;
int i;
pthread_create(&tid,NULL,func,NULL);
sleep(5); //5秒内不能被取消,可以执行完上述代码
pthread_cancel(tid);
while(1){
sleep(1);
}
}
总结:
能不能取消却决于有没有取消点,pthread_testcancel(),同时设置是否能被取消。
如果不能被取消,则有没有取消点都没有关系了。
int pthread_setcanceltype(int type, int *oldtype);
PTHREAD_CANCEL_DEFERRED 等到取消点才取消
PTHREAD_CANCEL_ASYNCHRONOUS 目标线程会立刻取消
问题:如果线程取消了,代码没有正常退出,内存没有释放,会造成内存泄漏怎么解决?
必要性:当线程非正常终止,需要清理一些资源。
void pthread_cleanup_push(void (*routine) (void *), void *arg)
void pthread_cleanup_pop(int execute)
两个函数需要成对出现再代码中,否则会出错:
#include
#include
void cleanup(void *arg)
{
printf("cleanup=%s\n",(char*)arg);
}
void *func(void *arg)
{
printf("This is child thread\n");
pthread_cleanup_push(cleanup,"abcd");
pthread_exit("thread return");
}
int main()
{
pthread_t tid;
void *retv;
int i;
pthread_create(&tid,NULL,func,NULL);
sleep(1);
pthread_cancel(tid);
pthread_join(tid,&revt);
while(1)
{
sleep(1);
}
}
linux@linux:~/Desktop$ gcc -g -o pcancel pcancel.c -lpthread
pcancel.c: In function ‘func’:
pcancel.c:18:1: error: expected ‘while’ before ‘int’
int main()
^
pcancel.c:33:1: error: expected declaration or statement at end of input
}
^
pcancel.c:33:1: error: expected declaration or statement at end of input
查看vim /usr/include/pthread.h
正确用法:
#include
#include
#include
void cleanup(void *arg)
{
//做清理工作的代码
printf("cleanup=%s\n",(char*)arg);
}
void *func(void *arg)
{
printf("This is child thread\n");
pthread_cleanup_push(cleanup,"abcd");
pthread_exit("thread return"); //执行这句话,上面cleanup也会被调用到。
pthread_cleanup_pop(0); //此时这句话意义在于完成大括号结束,0代表删除函数
}
/*
void *func(void *arg)
{
printf("This is child thread\n");
pthread_cleanup_push(cleanup,"abcd");
pthread_cleanup_pop(1); // 非0参数执行pthread_cleanup_pop()
pthread_exit("thread return");
}
*/
int main()
{
pthread_t tid;
void *retv;
int i;
pthread_create(&tid,NULL,func,NULL);
sleep(1);
pthread_cancel(tid);
pthread_join(tid,&revt);
while(1)
{
sleep(1);
}
}
//运行结果
linux@linux:~/Desktop$ ./pcancel
This is child thread
cleanup=abcd
routine 函数被执行的条件:
注意:
#include
#include
#include
void cleanup(void *arg){
printf("cleanup,arg=%s\n",(char*)arg);
}
void cleanup2(void* arg){
printf("cleanup2,arg=%s\n",(char*)arg);
}
void *func(void *arg){
printf("This is child thread\n");
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL); //设置了遇到取消点立刻取消线程
pthread_cleanup_push(cleanup,"abcd");
pthread_cleanup_push(cleanup2,"efgh");
//while(1)
{
sleep(1);
}
pthread_cancel(pthread_self()); //取消线程,也会触发回调
printf("Should not print\n");
while(1){
printf("sleep\n");
sleep(1);
}
pthread_exit("thread return");
pthread_cleanup_pop(1);
pthread_cleanup_pop(1);
sleep(10);
pthread_exit("thread return");
}
int main(){
pthread_t tid;
void *retv;
int i;
pthread_create(&tid,NULL,func,NULL);
sleep(1);
// pthread_cancel(tid);
pthread_join(tid,&retv);
//printf("thread ret=%s\n",(char*)retv);
while(1){
sleep(1);
}
}
//运行结果
linux@linux:~/Desktop$ ./pcancel
This is child thread
cleanup2,arg=efgh
cleanup,arg=abcd
如改变一下:return和 可以结束线程,也可以给pthread_join返回值,但不能触发pthread_cleanup_push里面的回调函数,所以我们结束线程尽量使用pthread_exit退出线程。
#include
#include
#include
void cleanup(void *arg){
printf("cleanup,arg=%s\n",(char*)arg);
}
void cleanup2(void* arg){
printf("cleanup2,arg=%s\n",(char*)arg);
}
void *func(void *arg){
printf("This is child thread\n");
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);
pthread_cleanup_push(cleanup,"abcd");
pthread_cleanup_push(cleanup2,"efgh");
//while(1)
{
sleep(1);
}
// pthread_cancel(pthread_self());
//printf("Should not print\n");
return "1234";
while(1){
printf("sleep\n");
sleep(1);
}
pthread_exit("thread return");
pthread_cleanup_pop(1);
pthread_cleanup_pop(1);
sleep(10);
pthread_exit("thread return");
}
int main(){
pthread_t tid;
void *retv;
int i;
pthread_create(&tid,NULL,func,NULL);
sleep(1);
// pthread_cancel(tid);
pthread_join(tid,&retv);
printf("thread ret=%s\n",(char*)retv);
while(1){
sleep(1);
}
}
练习
void pthread_cleanup_push(void (*routine) (void *), void *arg)
void pthread_cleanup_pop(int execute) 的本质是什么?D
A. 两个函数
B. pthread_cleanup_push函数可以取消一个线程
C. pthread_cleanup_pop函数可以清理一个线程
D. 两个宏定义,必须配对使用