操作系统_并发_2.1_线程创建细解
1、线程创建函数
编写多线程程序的第一步就是创建新线程,因此必须存在某种线程创建接口。
下面是phread.h
库文件中声明的线程创建函数pthread_create()
int pthread_create( pthread_t *th,
const pthread_attr_t *attr,
void *(* func)(void *),
void *arg);
-
第一个参数
th
th
是指向pthread_t
结构类型的指针,我们利用这个结构与该线程交互,因此需要将它传入pthread_create()
,以便于将它初始化 -
第二个参数
attr
attr
参数用于指定该线程可能具有的任何属性。一些例子包括设置栈大小,或关于该线程调度优先级的信息。
一个属性通过单独调用pthread_attr_init()
来初始化。 -
第三个函数指针参数
void *(* func)(void *)
第三个参数的作用是,这个线程应该在哪个函数中运行,这个指针主要包含以下内容:一个函数名称,它被传入一个类型为void*
的参数,并且它返回一个void*
类型的值。
如果这个函数需要一个整数参数,则声明为整型
int (*start_routine)(void *)
-
第四个参数
arg
arg
是要传递给线程开始执行的函数的参数。
2、创建线程
创建线程示例
我们创建单个线程,并通过myarg_t
结构传递一些参数。对于返回值,使用myret_t
类型。
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
typedef struct myarg_t{
int a;
int b;
}myarg_t;
typedef struct myret_t{
int x;
int y;
}myret_t;
void *mythread(void *arg){
myarg_t *m = (myarg_t *) arg;
printf("%d %d\n",m->a,m->b);
myret_t *r = malloc(sizeof(myret_t));
r->x = 1;
r->y = 2;
return(void *) r;
}
int main(int argc,char *argv[]){
int rc;
pthread_t p;
myret_t *m;
myarg_t args;
args.a = 10;
args.b = 20;
pthread_create(&p,NULL,mythread,&args);
printf("return %d %d\n",m->x,m->y);
return 0;
}
程序运行结果
编译运行后,带有return
的输出函数并没有输出数据,Why?
两种可能,一种是线程未执行完毕,另一种是,线程的值没有返回,我们具体来看一下。
3、线程完成
在上面,我们展示了如何创建一个线程,但是,如果想要等待线程完成,需要做一些特别的事情来等待完成,这里我们通过调用函数pthread_join
函数来实现。
当线程执行完毕后,线程已经在pthread_join()
函数内等待了,然后会返回,我们可以访问线程返回的值,也就是在myret_t
中的内容。
int pthread_join(pthread_t t, void **res);
该函数有两个参数
第一个pthread_t
类型的参数是用于指定要等待的线程,这个变量是由线程创建函数初始化的。
第二个参数是一个指针,指向你希望得到的返回值。如果不需要参数,创建线程时传入NULL
就可以了
加入等待线程完成后的代码
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
typedef struct myarg_t{
int a;
int b;
}myarg_t;
typedef struct myret_t{
int x;
int y;
}myret_t;
void *mythread(void *arg){
myarg_t *m = (myarg_t *) arg;
printf("%d %d\n",m->a,m->b);
myret_t *r = malloc(sizeof(myret_t));
r->x = 1;
r->y = 2;
return(void *) r;
}
int main(int argc,char *argv[]){
int rc;
pthread_t p;
myret_t *m;
myarg_t args;
args.a = 10;
args.b = 20;
pthread_create(&p,NULL,mythread,&args);
pthread_join(p,(void **) &m);
printf("return %d %d\n",m->x,m->y);
return 0;
}
线程运行结果如下
从上面的图可以看出,得到了线程的返回值。
不过需要注意的是,必须非常小心如何从线程返回值,特别是,永远不要返回一个指针,并让它指向线程调用栈上分配的东西。我们来试一下,看会发生什么糟糕的现象。
4、线程返回一个指针
void *mythread(void *arg){
myarg_t *m = (myarg_t *) arg;
printf("%d %d\n",m->a,m->b);
myret_t r;
r.x = 1;
r.y = 2;
return (void *) &r;
}
执行结果
我们将变量r
分配在mythread
的栈上,但是当它返回时,该值会自动释放。
5、奇怪的方式
对于我们上面的示例,使用pthread_create()
创建线程,然后立刻调用pthread_join()
,这是创建线程的一种非常奇怪的方式。
事实上,有一个被称为过程调用的方法,可以更简单的完成这个任务。
需要注意到的一点是,并非所有的多线程代码使用join
函数。
对于web
服务器可能会创建大量工作线程,然后使用主线程接受请求,并将其无限期地传递给工作线程,因此这样的长期程序可能不需要join
。
但,当创建线程来并行
执行特定任务的并行程序,很可能会使用join
来确保在退出或进入下一阶段计算之前完成所有这些工作。