文章目录
- 前言
- 线程的概念
- 线程的操作
- 操作的原理
- 补充与说明
前言
① 函数的具体说明被放在补充与说明部分
② 只说些基础概念和函数使用
线程的概念
网络回答:Linux 线程是指在 Linux 操作系统中创建和管理的轻量级执行单元。线程是进程的一部分,与进程共享同一地址空间和文件描述符等资源,但拥有独立的程序计数器、栈和寄存器等执行上下文。线程可以并发执行,实现多任务处理。
个人理解:Linux中,进程是承担资源分配的实体,换一种说法,一个进程占用一部分硬件资源。 CPU调度进程,或者说运行进程依靠的是task_struct来访问进程所占用的资源。所以在CPU视角来看,task_struct就是CPU识别”进程”的唯一信息。 如果给在一个进程内创建多个task_struct, CPU就会认为这些task_struct是不同的“进程“(这就是轻量级进程), 会把CPU算力资源分配给这些task_struct, 但实际上这些task_struct对应都是同一进程资源。 其实Linux没有真正意义上的线程结构,Linux是利用PCB结构模拟的线程。 也就是说Linux中的轻量级进程就是线程。 也因此,在后续Linux线程操作的学习中可以发现:Linux没有系统接口,而是在用户层提供了pthread原生线程库。
线程的操作
//创建一个新的线程int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg) //获取本线程的线程ID,这里的线程ID指的是pthread_t类型pthread_t pthread_self(void);//终止线程。value_ptr为传递给回收已终止线程的线程void pthread_exit(void *value_ptr);//“取消”一个执行中的线程int pthread_cancel(pthread_t thread);//线程等待,用于回收已经退出的线程int pthread_join(pthread_t thread, void **value_ptr);//线程分离,和 pthread_join 不同的是不关心线程的返回值int pthread_detach(pthread_t thread);
使用范例:
#include #include void* thread_func(void* arg) {int thread_num = *(int*)arg;printf("Thread %d is running\n", thread_num);pthread_exit(NULL);}int main() {pthread_t thread;int thread_arg = 1;// 创建线程int ret = pthread_create(&thread, NULL, thread_func, &thread_arg);if (ret != 0) {printf("Failed to create thread\n");return 1;}// 获取本线程的线程IDpthread_t self_thread = pthread_self();printf("Self thread ID: %lu\n", self_thread);// 等待线程结束并回收资源void* thread_result;ret = pthread_join(thread, &thread_result);if (ret != 0) {printf("Failed to join thread\n");return 1;}// 分离线程/* 一个线程不能既是分离的又是joinable的ret = pthread_detach(thread);if (ret != 0) {printf("Failed to detach thread\n");return 1;}*/printf("Main thread is exiting\n");return 0;}
操作的原理
- 线程组
在线程的概念部分解释到,①一个进程里可能有多个线程②Linux无实际的线程,我们使用的线程函数实际来自于位于用户层的原生线程库。 所以这里引入线程组的概念,线程组 = 多线程的进程 ;
struct task_struct {...pid_t pid; // 线程IDpid_t tgid;// 线程组ID//也就是ps -l 指令看到的PID...struct task_struct *group_leader;...struct list_head thread_group;...};
ps -eLf
指令的结果PID对应结构体中的tgid, LWP对应pid ;
另外,NLWP为线程组内线程的个数,线程ID(LWP)和进程ID(PID)相同的线程为主线程。
- 线程ID
pthread_t pthread_self(void)
函数返回的pthread_t和上述的LWP不是一个东西。因为Linux无实际的线程,我们使用的线程函数实际来自于位于用户层的原生线程库(动态库),所以实际CPU调度过程为:CPU -> task_struct -> 进程地址空间 -> 加载在内存中的动态线程(内存资源)
。 pthread_t类型实际为task_struct通过进程地址空间访问共享动态库指定位置的指针。 原理图如下:
所以综上所述LWP值得是task_struct结构体中的一个属性,而pthread_t是task_struct通过进程地址空间访问共享动态库指定位置的指针,它们口头上都被称为线程ID,但本质有很大区别。(pthread_t主要在写代码时被称为线程ID)。
- pthread_cancel() 和 pthread_exit() 的区别
pthread_cancel()函数用于取消指定线程的执行。当调用pthread_cancel()函数时,它会发送一个取消请求给指定线程,但线程是否真正被取消取决于线程内部的取消点。线程可以在取消点处检查取消请求并决定是否继续执行或者终止。这个函数可以在任何线程中调用,包括主线程。
pthread_exit()函数用于终止当前线程的执行,并返回一个指定的退出码。当调用pthread_exit()函数时,当前线程会立即终止,并且不会继续执行后续的代码。这个函数只能在当前线程中调用,用于退出当前线程。
- 线程终止
线程终止有三种方法:
①线程函数return返回
②线程调用pthread_exit()
③同一线程组内线程调用pthread_cancle()终止指定线程 。
- 线程等待
线程终止后的有两种处理方式,
①对待使用pthread_detach()进行线程分离的线程,线程资源会被自动回收,其他线程不关心被分离线程的返回结果。
② 对待一般的线程终止,需要有其他线程调用prtread_jion函数对其进行处理。
因为一般线程终止后其在进程地址空间中所占用的线程库资源未被释放,需要pthread_jion函数去释放,且一般线程终止后的结果也需要pthread_join函数接收,对pthread_join函数接收结果分析,可得到线程终止的原因和运行结果。
注意:线程分离和等待是冲突的,线程被设置pthread_detach() 就不可以用pthread_jion函数去等待该进程结束。
- 主线程
主线程在进程启动时自动创建,并且在进程结束时自动退出。当主线程执行完所有的代码后,进程会自动终止,不需要显式地调用pthread_exit()函数或者其他线程等待主线程。 主线程也可以其他线程结束前手动结束。
主线程几乎没什么特别之处,线程和进程不一样,进程有父进程的概念,但在线程组里面,所有的线程都是对等关系。
补充与说明
- pthread_create
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)
是一个 POSIX 线程库中的函数,用于创建一个新的线程。
参数说明:
thread:指向 pthread_t 类型的指针,用于存储新线程的标识符。
attr:指向 pthread_attr_t 类型的指针,用于指定新线程的属性,可以为 NULL,表示使用默认属性。
start_routine:是一个函数指针,指向新线程要执行的函数。该函数的返回类型是 void *,接受一个 void * 类型的参数。
arg:是一个 void * 类型的参数,作为 start_routine 函数的参数传递给新线程。
返回值:
如果成功创建新线程,则返回 0。
如果发生错误,则返回一个非零的错误代码,表示创建线程失败。
pthread_create 函数的作用是在调用它的线程中创建一个新的线程。新线程会立即开始执行 start_routine 函数,并使用 arg 作为参数传递给 start_routine 函数。线程的创建是异步的,即 pthread_create 函数会立即返回,不会等待新线程的结束。
#include #include void* thread_func(void* arg) {int* value = (int*)arg;printf("Hello from thread! Value: %d\n", *value);pthread_exit(NULL);}int main() {pthread_t thread;int value = 10;// 创建线程int result = pthread_create(&thread, NULL, thread_func, &value);if (result != 0) {printf("Failed to create thread.\n");return 1;}// 等待线程结束result = pthread_join(thread, NULL);if (result != 0) {printf("Failed to join thread.\n");return 1;}printf("Thread finished.\n");return 0;}
- 错误检查:
传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。
pthreads函(库)数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)。而是将错误代码通过返回值返回pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误,建议通过返回值业判定,因为读取返回值要比读取线程内的errno变量的开销更小
- pthread_exit
pthread_exit 函数是 POSIX 线程库中的一个函数,用于终止当前线程的执行并返回一个值。它的函数原型如下:
void pthread_exit(void *value_ptr);
该函数接受一个指向任意类型的指针 value_ptr,用于传递线程的返回值。线程的返回值可以通过其他线程使用 pthread_join 函数来获取。
当一个线程调用 pthread_exit 函数时,它会立即终止自身的执行,并将 value_ptr 指向的值作为线程的返回值。其他线程可以通过 pthread_join 函数来等待该线程的终止,并获取它的返回值。
需要注意的是,如果线程在调用 pthread_exit 函数之前没有调用 pthread_detach 函数将自己分离,那么它的资源(如栈空间)将不会被释放,从而可能导致资源泄漏。
另外,调用 pthread_exit 函数并不会终止整个进程,只会终止当前线程的执行。如果想要终止整个进程,可以使用 exit 函数。
- pthread_cancel
pthread_cancel 函数是 POSIX 线程库中的一个函数,用于请求取消指定线程的执行。它的函数原型如下:
int pthread_cancel(pthread_t thread);
该函数接受一个 pthread_t 类型的参数 thread,用于指定要取消的线程。
当调用 pthread_cancel 函数时,它会向指定的线程发送一个取消请求。被取消的线程会在某个取消点(cancellation point)处终止执行,并根据取消类型的设置进行相应的处理。
取消请求的处理方式取决于被取消线程的取消状态和取消类型的设置。取消状态有三种可能的取值:
PTHREAD_CANCEL_ENABLE:允许线程被取消。
PTHREAD_CANCEL_DISABLE:禁止线程被取消。
PTHREAD_CANCEL_DEFERRED:推迟取消请求,直到线程到达取消点。
取消类型有两种可能的取值:
PTHREAD_CANCEL_ASYNCHRONOUS:异步取消。取消请求立即生效,被取消线程无法进行清理操作。
PTHREAD_CANCEL_DEFERRED:推迟取消。取消请求推迟到线程到达取消点时生效,被取消线程有机会进行清理操作。
需要注意的是,pthread_cancel 函数只是向目标线程发送取消请求,并不能保证目标线程会立即终止执行。被取消的线程需要在代码中显式地检查取消请求,并在适当的时候调用 pthread_exit 函数来终止自身的执行。
- thread_join
thread_join()函数是用于等待指定线程的结束,并获取线程的返回值。
它的参数包括:
thread:要等待的线程标识符(pthread_t类型),通常是通过调用pthread_create()创建线程时返回的标识符。
value_ptr:一个指向指针的指针,用于接收线程的返回值。线程的返回值是一个void*类型的指针,通过value_ptr传递给调用者。
pthread_join()函数会阻塞调用它的线程,直到指定的线程结束。一旦指定的线程结束,pthread_join()函数会返回,并且可以通过value_ptr获取线程的返回值。
需要注意的是,如果不关心线程的返回值,可以将value_ptr参数设置为NULL。
pthread_join()函数的返回值为0表示成功,非0值表示失败。
使用pthread_join()函数可以确保在主线程中等待其他线程的结束,以免主线程提前退出导致其他线程无法完成任务。