-
线程概念
在传统操作系统中,进程就是一个运行中程序的描述信息–PCB 控制程序的运行
Linux系统并没有对线程设计一个TCB来控制线程的运行
在Linux下,线程以进程PCB模拟实现,也就是说在Linux下PCB其实是一个线程
在Linux下,线程其实是一个轻量级进程
Linux进程实际是一个线程组 当中包含一个或者多个线程
因为CPU调度程序运行时调度PCB,因此线程是CPU调度的最小单位
因为一个程序运行会分配资源给线程组, 所以进程是资源分配的最小单位
-
多进程和多线程优缺点对比
-
线程优缺点
-
都可以并发/并行处理任务,提高处理效率
-
对临界资源操作需要考虑更多, 编程更加复杂
-
一个进程中的线程共用同一个虚拟地址空间
vfork()创建一个子进程共用一个虚拟地址空间,怕出现调用栈混乱,因此子进程运行完毕或程序替换后父进程才开始运行
多线程PCB使用同一个虚拟地址空间,如何实现同时运行而不会出现调用栈混乱
为每个线程在虚拟地址空间单独分配一块空间
-
优点
- 线程间通信更加方便
- 线程的创建销毁成本更低
- 线程间切换调度成本更低
- 线程的执行粒度更细
-
缺点
- 线程之间缺乏访问控制, 健壮性低
- 系统调用(exit) 异常针对的是整个进程产生效果
-
-
进程优缺点
-
cpu密集型程序
程序中都是大量运算操作
-
io密集型程序
程序中都是大量io操作
-
-
-
线程控制
线程创建、线程终止、线程等待、线程分离
创建线程是一个用户态线程,在内核中对应了一个轻量级进程实现程序的调度运行
-
同一个进程的线程之间独有的数据
- 栈
- 寄存器
- errno
- 信号屏蔽字
- 线程标识符
-
同一个进程的线程之间共享数据
-
数据段。代码段
-
文件描述符表
-
信号的处理方式
-
-
线程创建
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg); 参数 thread:返回线程ID attr:设置线程的属性,attr为NULL表示使用默认属性 start_routine:是个函数地址,线程启动后要执行的函数 arg:传给线程启动函数的参数 返回值:成功返回0;失败返回错误码
注意: pthread函数出错时不会设置全局变量errno(而大部分POSIX函数会这样做)。而是将错误码通过返回值返回。pthread同样提供线程内的errno变量,以支持其它使用errno的代码。对于pthread函数的错误,建议通过返回值判定,因为读取返回值要比读取线程中的errno变量开销小
-
进程ID和线程ID
-
在Linux中,目前线程是Native POSIX Thread Libaray,简称NPTL。在这种实现下, 线程又被称为轻量级进程,每一个用户态的线程,在内核中都对应一个调度实体,也拥有自己的进程描述符(task_struct)结构体
-
没有线程之前,一个进程对应内核里的一个进程描述符,对应一个进程ID,但是引入线程后,一个用户进程下管辖N个用户态线程,每个线程作为一个独立的调度实体在内核态都有自己的进程描述符,进程和内核的描述符一下子就编程1:N关系,POSIX标准又要求进程内的所有线程调用gitpid函数时返回相同的进程ID
-
Linux内核引入线程组的概念
多线程的进程,又被称为线程组,线程组内每个线程在内核之中都存在一个进程描述符(task_struct)与之对应。进程描述符结构中的pid,表面上看对应的是进程ID,其实不然,对应的是线程ID;进程描述符中的tgid,含义是Thread Group ID,该值对应的是用户层面的进程ID
用户态 系统调用 内核进程描述符中对应的结构 线程ID pid_t gettid(void) pid_t pid 进程ID pid_t getpid(void) pid_t tgid 查看线程ID:
-
ps命令中的-L选项, 会显示:
- LWP:线程ID, 即gettid()系统调用的返回值
- NLWP:线程组内线程的个数
-
线程组ID总是和主线程的线程组ID一致,无论是主线程直接创建线程,还是创建出来的线程再次创建线程,都是这样。
-
-
线程ID及进程地址空间
pthread_create函数返回的线程ID,是供给用户调度的与进程调度的线程ID不同
pthread_create函数第一个参数指向一个虚拟内存单元,该内存单元地址即为新创建线程的线程ID。
-
-
获取线程id
pthread_t pthread_self(void) // 获取当前线程id
pthread_t 类型:在Linux目前实现的NPTL实现而言,pthread_t类型的线程ID,本质是一个进程地址空间上的一个地址。
-
线程终止
-
线程入口函数中return;main中不能return,否则退出的是进程
-
pthread_exit(void* retval) 主动退出,谁调用谁退出
-
pthread_cance(tid) 被动退出,取消指定的线程
#include <stdio.h> #include <pthread.h> #include <stdlib.h> #include <string.h> void* thr_start(void *arg) { // printf("thread start!\n"); // sleep(5); // pthread_exit("i did exit"); while (1) { printf("i am new thread![%s][%d]\n", arg, (int)pthread_self()); sleep(1); } // int* p = (int*)malloc(sizeof(int)); // *p = 1; // return (void*)p; } int main() { pthread_t tid; int ret = pthread_create(&tid, NULL, thr_start, (void*)"for new"); if (ret != 0) { fprintf(stderr, "thread create error: [$s]\n", strerror(ret)); return -1; } // char exit[200] = {0}; // 不能使用数组形式的char数组 // 如果线程是pthread_exit()退出, pthread_join接受的是pthread_exit函数中参数值 // char* exit = NULL; // pthread_join(tid, (void**)&exit); // printf("exit info: %s\n", exit); // 如果线程是return退出,pthread_join接受的是return的值 void* exit; // pthread_join(tid, &exit); // printf("exit info: %d\n", *(int*)exit); // 如果线程是通过pthread_cancel 退出,pthread_join接受的是PTHREAD_CANCELED sleep(5); pthread_cancel(tid); pthread_join(tid, &exit); if (exit == PTHREAD_CANCELED) { printf("exit info: PTHREAD_CANCELED\n"); } else { printf("exit info: NULL"); } while (1) { printf("i am main thread[%d]\n", (int)pthread_self()); sleep(1); } return 0; }
线程退出,也会为了保存退出返回值,而成为僵尸线程
主线程退出,进程不会退出
-
-
线程等待
获取指定退出线程的返回值,并且允许操作系统回收线程资源
注意: 一个线程启动之后,默认有一个属性是线程处于joinable状态;
处于joinable状态的线程, 退出后,不会自动释放资源,需要被其他线程等待
int pthread_join(pthread_t thread, void** retval) thread: 指定的线程ID retval: 用于获取线程退出返回值 return: 成功返回0 失败非0 errno 功能: 等待线程退出,获取返回值,回收线程资源 前提,这个被等待的线程必须处于joinable
-
线程分离
默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join, 否则无法释放资源,导致资源泄露
如果不关心线程的返回值,join就会是一种负担。所以就需要分离一个线程(设置线程的属性从joinable变为detach),线程退出后系统将自动回收资源,被分离的线程无法被等待
若是非要pthread_join则会直接报错返回
#include <pthread.h> int pthread_detach(pthread_t thread) thread: 指定的线程ID return 成功返回0 失败非0
-
线程属性
处于joinable状态的线程退出后不会自动释放资源需要被等待
处于detach状态的线程退出后自动回收资源,不需要被等待
线程默认的属性是joinable
Linux线程
最新推荐文章于 2024-08-17 09:28:44 发布