创建线程
Linux系统中创建线程的系统调用接口为pthread_create(),该函数存在于函数库pthread.h中,其声明如下:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
pthread_create()函数若调用成功,会返回0;若创建失败,则直接返回errno。此外,由于errno的值很容易被修改,线程中很少使用errno来存储错误码,也不会使用perror()直接将其打印,而是使用自定义变量接收errno,再调用strerror()将获取到的错误码转换成错误信息,最后才打印错误信息。
pthread_create()函数中包含四个参数:参数thread表示待创建线程的线程id指针,这是一个传出参数,若需要对该线程进行操作,应使用一个pthread_t类型的变量获取该参数;参数attr用于设置待创建线程的属性,通常传入NULL,表示使用线程的默认属性;参数start_toutinue是一个函数指针,指向一个参数为void、返回值也为void*的函数,该函数为待创建线程的执行函数,线程创建成功后将会执行该函数中的代码;参数arg为要传给线程执行函数的参数。
在线程调用pthread_create()函数创建出新线程之后,当前线程会从pthread_create()函数返回并继续向下执行,新线程会执行函数指针start_routine所指的函数。若pthread_create()函数成功返回,新线程的id会被写到thread参数所指向的内存单元。
需要注意的是,进程id的类型pid_t实质是一个正整数,在整个系统中都是唯一的,但线程id只在当前进程中保证唯一,其类型pthread_t并非是一个正整数,且当前进程调用pthread_create()后获取到的thread为新线程id,因此线程id不能简单使用printf()函数打印,而应使用Linux提供的接口函数pthread_self()来获取。
pthread_self()函数存在于函数库pthread.h中,其声明如下:
pthread_t pthread_self(void);
下面通过一个案例来展示pthread_create()函数的用法。
案例1:使用pthread_create()函数创建线程,并使原线程与新线程分别打印自己的线程id。
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <pthread.h>
4 #include <unistd.h>
5 void *tfn(void *arg)
6 {
7 printf("tfn--pid=%d,tid=%lu\n",getpid(),pthread_self());
8 return (void*)0;
9 }
10 int main()
11 {
12 pthread_t tid;
13 printf("main--pid=%d,tid=%lu\n",getpid(),pthread_self());
14 int ret=pthread_create(&tid,NULL,tfn,NULL);
15 if(ret!=0){
16 fprintf(stderr,"pthread_create error:%s\n",strerror(ret));
17 exit(1);
18 }
19 sleep(1);
20 return 0;
21 }
若像之前的案例一样,直接使用gcc命令编译该案例,会出现以下提示:
/tmp/ccyC2hU1.o: In function `main':
pthread_cre.c:(.text+0x85): undefined reference to `pthread_create'
collect2: ld returned 1 exit status
这是因为,pthread库不是Linux系统默认的库,因此在使用pthread_create()函数创建线程时应链接静态库libpthread.a。案例1的文件名为pthread_cre.c,如下所示,在gcc命令中添加-lpthread参数,对案例1进行编译:
gcc pthread_cre.c -o pthread_cre -l pthread
执行程序,终端打印的信息如下:
main--pid=2881,tid=140132338001664
tfn--pid=2881,tid=140132337993472
在案例1的执行结果中,进程2881中的两个线程分别打印出了各自的线程id,由此可知案例1实现成功。
进程拥有独立的地址空间,当使用fork()函数创建出新进程后,若其中一个进程要对fork()之前的数据进行修改,进程中会依据“写时复制”原则,先复制一份该数据到子进程的地址空间,再修改数据,因此即便是全局变量,在进程间也是不共享的。但由于线程间共享地址空间,因此在一个线程中对全局区的数据进行修改,其它线程中访问到的也是修改后的数据。下面通过一个简单案例对此进行验证。
案例2:创建子线程,在子线程中修改原线程中定义在全局区的变量,并在原线程中打印该数据。
1 #include <stdio.h>
2 #include <pthread.h>
3 #include <stdlib.h>
4 #include <unistd.h>
5 int var = 100;
6 void *tfn(void *arg)
7 {
8 var = 200;
9 printf("thread\n");
10 return NULL;
11 }
12 int main(void)
13 {
14 printf("At first var = %d\n", var);
15 pthread_t tid;
16 pthread_create(&tid, NULL, tfn, NULL);
17 sleep(1);
18 printf("after pthread_create, var = %d\n", var);
19 return 0;
20 }
以上程序的主线程中定义了一个全局变量var,并值赋为100;随后在子线程中修改全局变量var的值,并使主线程沉睡1秒,等待子线程执行,确保主线程的打印语句在子进程功能完成后执行;最后在主线程中打印var的值。编译案例2,执行程序,终端打印的结果如下:
At first var = 100
thread
after pthread_create, var = 200
由打印结果可知,主线程中访问到的变量var的值被修改为200,说明子线程成功修改了主线程中定义的全局变量,线程之间共享全局数据。