
【linux多线程】创建多线程基础,多线程pthread_join阻塞和pthread_attr_setdetachstate线程分离的区别与深度解析
thread:线程ID,通过 pthread_t 定义。attr:线程属性,可以为线程设置各种属性,详情见附录。默认设置为NULL,表示使用默认的属性,即主子线程之间是可接合的。start_routine:子线程函数,必须是 void *func(void *)arg:子线程函数的参数,必须是 void *,如果需要其他类型,在引入参数时强转类型即可。输出结果:第一步:代码创建了线程,第二个参数
Linux多线程基础
一、创建多线程基础
1.函数接口
int phread_create(pthread_t *thread, const phtread_attr_t *attr,
void *(*start_routine)(void *), void *arg)
接口参数:
thread:线程ID,通过 pthread_t 定义。
attr:线程属性,可以为线程设置各种属性,详情见附录。默认设置为NULL,表示使用默认的属性,即主子线程之间是可接合的。
start_routine:子线程函数,必须是 void *func(void *)
arg:子线程函数的参数,必须是 void *,如果需要其他类型,在引入参数时强转类型即可。
代码实例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
void *child_thread(void *arg)
{
pthread_t ccid = pthread_self();
printf("child_thread ccid:%d\n", ccid);
int vec[5] = {1, 2, 3, 4, 5};
for(int i=0; i<5; i++)
printf("%d\t", i);
printf("\n");
return arg;
}
int main()
{
printf("main thread!\n");
//1.创建子线程
pthread_t cid;
if(pthread_create(&cid, NULL, child_thread, (void *)"i am coming!") != 0)
perror("create thread failed!\n");
//2.打印父子线程的线程ID
pthread_t pid = pthread_self();
printf("pid:%d; cid = %d\n", pid, cid);
//3.等待子线程退出并获取子线程的返回值
void *cret = NULL;
pthread_join(cid, &cret);
printf("cret: %s\n", (char *)cret);
printf("main thread over!\n");
return 0;
}
输出结果:
2.代码分析
第一步:代码创建了线程,第二个参数 attr 线程属性设置为 NULL,表示子线程与主线程是可接合的,子线程的资源由主线程回收。最后一个参数调用者使用的是 const char*类型所以要强制转换为void*类型。
第三步:多线程一个最重要的问题就是 保证主线程的生存期一定要大于或等于子线程的生存期(以return为标志)
那么如何保证主线程的生存期大于子线程呢?大家最先想到的就是 sleep() 函数,通过让主线程睡眠几秒,然后在这几秒内让子线程运行完毕。那么在这里是否能用 sleep() 函数延迟主线程的生存期呢?答案是可以的。
int main()
{
printf("main thread!\n");
//1.创建子线程
pthread_t cid;
if(pthread_create(&cid, NULL, child_thread, (void *)"i am coming!") != 0)
perror("create thread failed!\n");
//2.打印父子线程的线程ID
pthread_t pid = pthread_self();
printf("pid:%d; cid = %d\n", pid, cid);
//3.等待子线程退出或者说等待其运行完毕
sleep(1);
printf("main thread over!\n");
return 0;
}
输出结果:
可以看到,这里用sleep函数一样实现了子线程的任务。
3.pthread_join()的使用
(1)pthread_join()的第一个作用:阻塞等待
除了sleep之外, #include <pthread.h> 中提供一种标准的,专业的用于阻塞等待子线程退出的函数接口,就是我们本文的重点之一,pthread_join() 函数。
int phtread_join(pthread_t thread, void **retval)
接口参数:
pthread:子线程ID
retval:存储线程退出值的内存的指针
接口解析:
pthread_join()函数指定的线程如果还在运行,就会阻塞等待。这种功能就决定了其可以代替sleep()函数成为标准的线程等待函数。
那么此时就有个疑问:sleep()既然能代替pthread_join()那还要这个接口干什么?
由此推测:pthread_join()必定还有其他的作用。
(2)pthread_join()的第二个作用:获取子线程返回值
pthread_join()函数第二个参数可以获取子线程的返回值,换句话说,可接合的主子线程之间是可以传递变量出来的。就如我们例子的第三步那样,通过pthread_join()函数将子线程的参数 void *arg 传递出来赋值给 void *cret。
注意,pthread_join()不可以获取子线程函数内的局部变量,换句话说,子线程不可以返回局部变量,因为子线程的局部变量在子线程结束后就失去了生存期,属于非法内存,访问会造成段错误。
所以pthread_join()除了可以获取子线程函数child_thread()的参数外,仅只能获取子线程堆区的变量,也就是malloc出来的变量(因为malloc的生存期是由程序员自己控制的)。这里的void*arg参数是主线程提供的,虽然也是局部变量,但是其内存的生存期是要大于子线程的,故可以被返回。
如果一条线程是可接合的,即默认属性,那么子线程在退出时不会释放自身的资源,从而成为僵尸进程,同时意味着该线程的返回值可以被其他线程获取。
void *child_cthread(void *arg)
{
char *p = (char *)malloc(20);
strcpy(p, "hello world!");
return (void *)p;
}
int main()
{
pthread_t cid;
if(pthread_create(&cid, NULL, child_cthread, (void *)"i am coming!") != 0)
perror("create thread failed!\n");
//等待子线程退出并获取子线程的返回值
void *cret = NULL;
pthread_join(cid, &cret);
printf("cret: %s\n", (char *)cret);
free(cret);
cret = NULL;
printf("main thread over!\n");
return 0;
}
获取子线程的堆区变量时,主线程一定要记得释放其资源。当主线程不需要获取子线程的堆区变量时,子线程自己一定要记得释放其资源。
当我们不需要获取子线程的返回值时,为了不让子线程变成僵尸进程,我们一般最好是使用线程分离属性。
二、线程分离
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)
接口参数:
attr:线程属性变量
detachstate:PTHREAD_CREATE_DETACHED 表示分离,PTHREAD_CREATE_JOINABLE 表示结合
接口功能:
设置了线程分离属性,子线程在退出时会自动释放资源,也就是说子线程的返回值是无法被主线程捕捉的。子线程自动释放资源避免了成为僵尸进程。但是线程分离就需要程序员自己确保主线程与子线程的生存期,避免因生存期问题造成子线程无法运行。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
void *child_thread(void *arg)
{
pthread_t ccid = pthread_self();
printf("child_thread ccid:%d\n", ccid);
int vec[5] = {1, 2, 3, 4, 5};
for(int i=0; i<5; i++)
printf("%d\t", i);
printf("\n");
return arg;
}
int main()
{
printf("main thread!\n");
//1.设置线程分离属性,让子线程自动回收内存
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
//2.创建线程
pthread_t cid;
int ret = pthread_create(&cid, &attr, child_thread, (void *)"i am come coming");
if(ret)
perror("create pthread error\n");
sleep(1);//给子线程提供足够的生存期
//pause();//暂停主线程,目的也是给子线程提供足够的生存期
// void *cret;
// pthread_join(cid, &cret);
// printf("cret: %s\n", (char *)cret);//线程分离之后是无法捕获子线程的返回值的
printf("main thread end !\n");
return 0;
}
当多线程线程分离的时候,如何确定子线程的生存期和线程安全是非常重要的。在更大的项目中,光靠sleep是无法满足需求的,有些项目甚至不能阻塞。所以还需要学习更加进阶的线程知识。
更多推荐










所有评论(0)