线程控制
通过代码实现线程的创建/退出/等待/分离 进行线程控制的接口代码,都来自库函数, 也就是说,操作系统没有提供创建线程的接口,大佬们人为的使用库函数封装了一套线程库。 这套封装的线程库函数,提供线程的各种操作。 使用库函数创建一个线程(用户态),本质上线程就会在内核中创建一个轻量化的进程来实现程序的调度。 (pcb是一个task_struct结构体,存储在内核中,使用的是内核空间,想要访问pcb就得切换到内核态运行。)
创建:
int pthread_create(pthread_t* thread,const pthread_attr_t* attr,void* (*start_routine)(void* ),void* arg);
函数参数:
thread:输出型参数,用于获取线程id -- 线程的操作句柄tid
attr:线程属性,用于在创建线程的同时设置线程属性,暂时通常置NULL
如: 栈大小 栈的溢出缓冲区大小 joinable属性/detach属性 阻塞信号的继承属性
start_routine:函数指针,就是一个线程的入口函数 -- 线程运行的就是这个函数,函数运行完毕,这个线程就会退出。但主函数是不会等待这个线程执行完毕的。这个函数返回NULL。
arg:通过线程的入口函数,传递给线程的参数。要作为参数传递给入口函数statr_routine。
返回值:成功返回0,失败返回非0值(错误编号)。
pthread_create接口的使用:
1
6
7
8 #include<stdio.h>
9 #include<unistd.h>
10 #include<string.h>
11 #include<pthread.h>
12
13 void* thr_start(void* arg)
14 {
15 strcpy(arg,"不好意思,你的参数被修改了~\n");
16 while(1)
17 {
18 printf("I am thread~----%s",(char*)str);
19 sleep(1);
20 }
21 return NULL;
22 }
23
24
25
26 int main()
27 {
28
29
30 pthread_t tid;
31 char ptr[] = "wohenhao~\n";
32 int ret = pthread_create(&tid,NULL,thr_start,(void*)ptr);
33 if(ret != 0){
34 printf("创建线程失败~\n");
35 return -1;
36 }
37 printf("创建线程成功~\n");
38
39 while(1)
40 {
41 printf("nihaoa~---%s\n",ptr);
42 sleep(1);
43 }
44
45
46
47 while(1)
48 {
49 printf("wobuhao~\n");
50 sleep(1);
51 }
52
53 return 0;
54 }
代码写好之后我们发现使用gcc编译提示pthread_create未定义,打开man pthread_create发现, 使用时这个接口时,在编译时应该使用-pthread链接。需要链接到pthread链接库。 注意这里的-l是为了适应平台,实际上直接加上-pthread也可以
库的链接使用:
1.将库文件放在指定的目录下:/lib64/usr/lib64
2.将库文件的路径添加到环境变量 LIBRARY_PATH中
3.使用gcc -L选项指定库文件所在路径 -l 指定库文件名称。
gcc默认是动态库
运行之后我们就可以看到: 相当于把主线程main函数的局部变量地址传递给了普通线程:经过同一块虚拟地址空间,同一块页表,其实映射的也是同一块物理内存,谁改变了这个物理内存的数据,另一边也就随之改变。 tid: 在上面代码中,我们在创建线程之前,用pthread_t 定义了一个tid作为pthread_create函数的第一个参数。 我们打印一下这个tid: 这是一个无符号长整形的数字。
那么这tid是什么呢? 其实,当我们创建一个线程时,会在虚拟地址空间上的栈区和堆区之间的共享区中开辟一块独属于这个线程的空间, 这块空间保存这个线程独有的数据,(比如线程的栈,线程在用户态的描述)。 这个空间就叫做这个线程的线程地址空间。tid就是这块空间的首地址。 系统通过向thread_create函数传入线程地址空间的首地址tid,来实现对这块空间的访问,从而实现对线程的控制。 所以这个tid在函数的说明中也叫线程的操作句柄。 这跟我们之前遇到的pid有什么关系/区别呢? 实际上,这两者并没有关联:
pid 是一个轻量级进程id,是内核中task_struct结构体中的id,
task_struct -> pid : 轻量级进程id,也就是 ps -efL看到的LWP
task_struct -> tgid: 线程组id,等于主线程id,也就是进程id,在ps -ef中表现为PID
同一个线程组的所有pcb都有一个相同的tgid
当我们程序运行起来时,使用ps-efL查看的PID 和 轻量化进程id与tid没有关系,tid仅用于对线程的操作。
轻量级进程与线程:
其实轻量级进程和线程之间还是有区别的,在用户态创建一个线程时,由于系统没有提供线程的概念, 我们需要在内核态创建一个pcb,pcb通过找到tid这个空间首地址实现对线程的操作。 只是因为我们在创建一个线程的同时需要在内核中创建一个轻量化的进程,这才把线程叫做轻量化的进程。其实两者是有区别的。 ps -efL 的LWP就是内核中的pcb轻量化进程,而不是线程,这点要注意区分。
|