目录
线程创建
线程终止
函数中return
函数中调用pthread_exit接口
函数中调用pthread_cancle接口取消目标线程
编辑
线程等待
线程分离
上期我们学习了线程的基本概念,本期我们将开始学习线程的相关操作。现成的操作分为,创建,终止,等待,分离。
线程创建
参数:thread,向其传入输出型参数的地址,输出型参数可以获取创建的线程的线程id。
attr,线程的属性,默认设置为NULL,为线程的默认属性。
start_routine,函数指针,向其传入创建的线程执行的函数的函数名。
arg,向其传入线程所执行的函数的实参。
返回值:创建成功返回0,创建失败返回错误码。
代码如下。
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>void* thread_run(void* arg)
{int id=*(int*)arg;while(1){printf("%d,当前线程的id:%lu\n",id,pthread_self());sleep(1);}
}#define NUM 5int main()
{pthread_t tid[NUM];for(int i=0;i<NUM;i++){pthread_create(tid+i,NULL,thread_run,(void*)&i);sleep(1); }while(1){};return 0;
}
在上述代码中我们使用了pthread_self()接口,这个接口可以获得当前线程的线程id。
运行结果如下。
通过指令查看创建的线程。
不知道大家有没有发现一个问题,在之前,我们认为LWP就是线程id,但是通过上述pthread_self()函数获取的线程id却和LWP不相同,这是为什么呢?
图示如下。
对于上图,我们就要从pthread线程库说起,pthread线程库,说到底也是一个文件,存储于磁盘之上,当把pthread线程库加载到内存之后,通过页表映射到不同的进程的进程地址空间内。
在操作系统内核中,我们认为线程的结构体就是pcb,这是在操作系统内核的概念,但是站在用户的角度,线程的结构体其实并不是pcb,可以认为在pthread线程库内部,有一个线程结构体数组,每一个结构体就占用了一块物理内存,通过页表映射到了进程地址空间中,此时进程地址空间中就有了大量的线程结构体,当一个进程内部,操作系统创建大量的线程时,内核每创建一个线程,就会在进程的进程地址空间中对应的线程库中的结构体数组中中分配一个结构体用于存储线程的产生的临时数据。
我们之前说过了线程是有独属于自己的数据的,比如栈,用于存储线程产生的临时数据,还有寄存器,用于存储自己的上下文数据。这些都是在进程地址空间中的线程结构体中存储的。说了这么多,那么这个线程id是啥,其实我们在使用接口时获得的pthread_t类型的线程id其实就是线程对应的结构体在进程地址空间中的相对地址。
其实,pthread线程库中的线程结构体和操作系统内核中的线程结构体pcb就是1:1对应的关系。
线程终止
线程终止分为三种情况。
函数中return
这个在之间进程终止中我们也接触过,我们说进程的退出分为两种情况,进程正常退出,结果正确和不正确,正确返回0,不正确返回非零。进程异常退出。
在线程这里使用return进行线程退出时,main函数中使用return 退出, 表示退出主线程,但是主线程退出,所有的线程都会跟着退出。其他线程的执行函数中退出,意味着当前线程退出。
代码如下。
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>void* thread_run(void* arg)
{int id=*(int*)arg;while(1){printf("%d,当前线程的id:%lu\n",id,pthread_self());sleep(1);break;}return(void*)0;
}#define NUM 5int main()
{pthread_t tid[NUM];for(int i=0;i<NUM;i++){pthread_create(tid+i,NULL,thread_run,(void*)&i);sleep(1); }sleep(10);return 0;
}
运行结果如下。
创建的每个线程在执行完自己的函数之后就会退出,所以我们只能看到主线程,主线程在休眠了10s之后也进行了退出。
但是需要注意的是,在主线程中,退出码是一个整型。但是在创建的线程执行的函数中,函数的返回值是void*类型。返回值可以不是整型,可以是其他的类型,但是最终都要转为void*类型。
函数中调用pthread_exit接口
参数:retval,是第一种线程退出方式中,return 返回的值。
返回值:无返回值。
代码如下。
void* thread_run(void* arg)
{int id=*(int*)arg;while(1){if(id==1){printf("%d,当前线程的id:%lu\n",id,pthread_self());sleep(1);pthread_exit((void*)123);}else{printf("%d,当前线程的id:%lu\n",id,pthread_self());sleep(1);}}
}#define NUM 5int main()
{pthread_t tid[NUM];for(int i=0;i<NUM;i++){pthread_create(tid+i,NULL,thread_run,(void*)&i);sleep(1); }sleep(10);return 0;
}
我们只是对创建的第二个线程进行了终止。
运行结果如下。
运行结果符合预期。
函数中调用pthread_cancle接口取消目标线程
参数:thread,表示要取消的线程的线程id。
返回值:成功返回0,失败返回错误码。
代码如下。
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>void* thread_run(void* arg)
{int id=*(int*)arg;while(1){if(id==1){printf("%d,当前线程的id:%lu\n",id,pthread_self());sleep(1);pthread_exit((void*)123);}else{printf("%d,当前线程的id:%lu\n",id,pthread_self());sleep(1);}}
}#define NUM 5int main()
{pthread_t tid[NUM];for(int i=0;i<NUM;i++){pthread_create(tid+i,NULL,thread_run,(void*)&i);sleep(1); }pthread_cancel(tid[0]);sleep(10);return 0;
}
运行结果如下。
本来我们创建了6个线程,但是因为推出了0号线程和1号线程,所以最终只剩下了四个线程。运行结果符合预期。
上述代码是在主线程中取消了其它线程,因为后续我们学习了线程等待的接口时,会对取消的线程进行等待,不会导致线程成为僵尸状态。我们同样可以使用其它线程取消主线程,但是不建议这样干,因为取消之后,bash进程不会再去等待主线程,导致主线程最终成为僵尸状态。
代码如下。
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
pthread_t g_id;void* thread_run(void* arg)
{int id=*(int*)arg;while(1){if(id==1){printf("%d,当前线程的id:%lu\n",id,pthread_self());sleep(5);pthread_cancel(g_id);}else{printf("%d,当前线程的id:%lu\n",id,pthread_self());sleep(1);}}
}#define NUM 5
int main()
{pthread_t tid[NUM];g_id=pthread_self();for(int i=0;i<NUM;i++){pthread_create(tid+i,NULL,thread_run,(void*)&i);sleep(1); }pthread_cancel(tid[0]);sleep(10);return 0;
}
通过上述代码可知,我们在1号线程内部取消了主线程。
运行结果如下。
我们发现,虽然取消了主线程,但是因为bash此时不再去等待主线程,所以主线程成为了僵尸态。所以我们不建议在其他线程中取消主线程,在主线程中取消其它线程,这是可行的。
线程等待
在进程控制中,我们学习了进程等待,父进程等待是为了防止子进程成为僵尸进程,线程等待同理,也是为了防止系统资源不被释放。
参数:thread,要等待的线程的线程id。
retval,一个输出型参数,用于获取等待的线程的退出信息。
返回值:成功返回0,失败返回错误码。
代码如下。
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>void* thread_run(void* arg)
{int id=*(int*)arg;while(1){printf("%d,当前线程的id:%lu\n",id,pthread_self());sleep(5);break;}return (void*)0;
}#define NUM 5
int main()
{pthread_t tid[NUM];void* status=NULL;for(int i=0;i<NUM;i++){pthread_create(tid+i,NULL,thread_run,(void*)&i);sleep(1); }for(int j=0;j<NUM;j++){pthread_join(tid[j],&status);printf("%d\n",(int)status);}sleep(10);return 0;
}
运行结果如下。
新创建了5个线程,最终5个线程退出,获取到了5个退出值,符合预期。
线程分离
线程等待是为了保证线程的资源在线程退出时被释放,有没有其它的方法,可以创建了线程之后,不用去等待线程,最终创建的线程的资源在线程退出时线程资源自动释放呢?当然有,就是线程分离。
参数:thread,要分离的线程的线程id。
返回值:分离成功返回0,分离失败返回错误码。
注意:分离的线程不能被主线程等待。
代码如下。
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>void* thread_run(void* arg)
{pthread_detach(pthread_self());int id=*(int*)arg;while(1){printf("%d,当前线程的id:%lu\n",id,pthread_self());break;sleep(1);}return (void*)1123;
}#define NUM 5
int main()
{pthread_t tid[NUM];void* status=NULL;for(int i=0;i<NUM;i++){pthread_create(tid+i,NULL,thread_run,(void*)&i);sleep(1); }sleep(2);for(int j=0;j<NUM;j++){pthread_join(tid[j],&status);printf("%d\n",(int)status);}sleep(10);return 0;
}
我们依次分离了创建的五个线程。
运行结果如下。
5个创建的线程的退出码都是1123,但是因为分离之后,创建的线程不能被主线程等待,所以等待的函数中的status是不能获取得到5个创建的线程的退出码的,所以最终打印出来的都是0,也都是空值,符合我们的预期。
线程控制的内容到此结束,因为与进程控制类似,所以学习起来难度不是特别大。
本期内容到此结束^_^