学科分类
目录
Linux编程

创建线程

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,说明子线程成功修改了主线程中定义的全局变量,线程之间共享全局数据。

点击此处
隐藏目录