持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第16天,点击查看活动详情
线程的概念
和多进程相比,多线程是一种比较节省资源的多任务操作方式。启动一个新的进程必须分配给它独立的地址空间,每个进程都有自己的堆栈段和数据段,系统开销比较高,进行数据的传递只能通过进程间通信的方式进行。在同一个进程中,可以运行多个线程,运行于同一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享全局变量和对象,启动一个线程所消耗的资源比启动一个进程所消耗的资源要少。
创建线程
在Linux下,采用pthread_create函数来创建一个新的线程,函数声明:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
参数thread为为指向线程标识符的地址。
参数attr用于设置线程属性,一般为空,表示使用默认属性。
参数start_routine是线程运行函数的地址,填函数名就可以了。
参数arg是线程运行函数的参数。新创建的线程从start_routine函数的地址开始运行,该函数只有一个无类型指针参数arg。若要想向start_routine传递多个参数,可以将多个参数放在一个结构体中,然后把结构体的地址作为arg参数传入,但是要非常慎重,程序员一般不会这么做。
在编译时注意加上-lpthread参数,以调用静态链接库。因为pthread并非Linux系统的默认库。
等待线程的结束
int pthread_join(pthread_t thread, void ** retval);
thread 参数用于指定接收哪个线程的返回值;retval 参数表示接收到的返回值,如果 thread 线程没有返回值,又或者我们不需要接收 thread 线程的返回值,可以将 retval 参数置为 NULL。
pthread_join() 函数会一直阻塞调用它的线程,直至目标线程执行结束(接收到目标线程的返回值),阻塞状态才会解除。
线程的终止
如果进程中的任一线程调用了exit,则整个进程会终止,所以,在线程的start_routine函数中,不能采用exit。
线程的终止有三种方式:
1)线程的start_routine函数代码结束,自然消亡。
2)线程的start_routine函数调用pthread_exit结束。
3)被主进程或其它线程中止。
pthread_exit函数的声明如下:
void pthread_exit(void *retval);
参数retval填空,即0。
example
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
void *mainfunc1(void *arg);
void *mainfunc2(void *arg);
int var = 0;
int main(){
pthread_t pthid1,pthid2;
if(pthread_create(&pthid1,NULL,mainfunc1,NULL)!=0){
printf("创建线程失败,程序退出\n");
return -1;
}
if(pthread_create(&pthid2,NULL,mainfunc2,NULL)!=0){
printf("创建线程失败,程序退出\n");
return -1;
}
printf("pthid1=%lu\n",pthid1);
printf("pthid2=%lu\n",pthid2);
printf("等待子线程退出\n");
pthread_join(pthid1,NULL);
printf("子线程1已退出\n");
pthread_join(pthid2,NULL);
printf("子线程2已退出\n");
return 0;
}
void *mainfunc1(void *arg){
for(int ii = 0;ii < 5;ii++){
sleep(1);
var++;
printf("mainfunc1 sleep 1sec ok(%d)\n",var);
}
pthread_exit(0);
}
void *mainfunc2(void *arg){
for(int ii = 0;ii < 5;ii++){
sleep(1);
var++;
printf("mainfunc2 sleep 1sec ok(%d)\n",var);
}
pthread_exit(0);
}
/*
pthid1=140402384905984
pthid2=140402376513280
等待子线程退出
mainfunc2 sleep 1sec ok(1)
mainfunc1 sleep 1sec ok(2)
mainfunc2 sleep 1sec ok(3)
mainfunc1 sleep 1sec ok(4)
mainfunc2 sleep 1sec ok(5)
mainfunc1 sleep 1sec ok(6)
mainfunc2 sleep 1sec ok(7)
mainfunc1 sleep 1sec ok(8)
mainfunc2 sleep 1sec ok(9)
mainfunc1 sleep 1sec ok(10)
子线程1已退出
子线程2已退出
*/
线程参数的传递
进程中的多个线程的数据空间是共享,共享带来了很多方便,也造成了很多麻烦(资源冲突)
所以在实际开发中经常用到参数的传递
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
void *mainfunc1(void *arg);
void *mainfunc2(void *arg);
int var = 1;
int main(){
pthread_t pthid1,pthid2;
if(pthread_create(&pthid1,NULL,mainfunc1,(void*)(long)var)!=0){
printf("创建线程失败,程序退出\n");
return -1;
}
var++;
if(pthread_create(&pthid2,NULL,mainfunc2,(void*)(long)var)!=0){
printf("创建线程失败,程序退出\n");
return -1;
}
printf("pthid1=%lu\n",pthid1);
printf("pthid2=%lu\n",pthid2);
printf("等待子线程退出\n");
pthread_join(pthid1,NULL);
printf("子线程1已退出\n");
pthread_join(pthid2,NULL);
printf("子线程2已退出\n");
return 0;
}
void *mainfunc1(void *arg){
printf("mainfunc1 sleep 1sec ok(%d)\n",(int)(long)arg);
for(int ii = 0;ii < 5;ii++){
sleep(1);
}
pthread_exit(0);
}
void *mainfunc2(void *arg){
printf("mainfunc2 sleep 1sec ok(%d)\n",(int)(long)arg);
for(int ii = 0;ii < 6;ii++){
sleep(1);
}
pthread_exit(0);
}
/*
pthid1=139900145309440
pthid2=139900136916736
等待子线程退出
mainfunc2 sleep 1sec ok(2)
mainfunc1 sleep 1sec ok(1)
子线程1已退出
子线程2已退出
*/
线程资源的回收
线程有joinable和unjoinable两种状态,如果线程是joinable状态,当线程主函数终止时(自己退出或调用pthread_exit退出)不会释放线程所占用内存资源和其它资源,这种线程被称为“僵尸线程”。创建线程时默认是非分离的,或者称为可连接的(joinable)。
避免僵尸线程就是如何正确的回收线程资源,有四种方法:
1)方法一:创建线程后,在创建线程的程序中调用pthread_join等待线程退出,一般不会采用这种方法,因为pthread_join会发生阻塞。
pthread_join(pthid,NULL);
2)方法二:创建线程前,调用pthread_attr_setdetachstate将线程设为detached,这样线程退出时,系统自动回收线程资源。
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED); // 设置线程的属性。
pthread_create(&pthid,&attr,pth_main,NULL);
3)方法三:创建线程后,在创建线程的程序中调用pthread_detach将新创建的线程设置为detached状态。
pthread_detach(pthid);
4)方法四:在线程主函数中调用pthread_detach改变自己的状态。
pthread_detach(pthread_self());