一.线程概念
- 定义:线程是程序执行的最小单位,是轻量级进程,通常用于在一个进程中并行执行多个任务。
- 资源分配:进程是系统中最小的资源分配单位,而线程则是系统中最小的执行单位。
1.线程的优点
- 资源节省:与多进程相比,线程因为共享同一进程的资源而更节省资源。
- 共享变量:线程间可以容易地共享变量,因为它们共享相同的内存空间。
2.线程的特征
- 共享资源:线程可以访问进程中的所有资源,包括数据段、堆等。
- 效率:线程的创建和切换比进程更高效,通常可以快约 30%。
- 三方库支持:
- 需要包含头文件
pthread.h
。 - 编译时需要链接
-pthread
库,如libpthread.so
。
- 需要包含头文件
3.线程的缺点
- 稳定性:线程由于共享地址空间,一旦发生错误可能影响整个进程的稳定性。
- 调试难度:多线程程序的调试相对复杂,需要跟踪线程的执行和同步状态。
4.线程与进程的区别
- 资源:
- 线程可以共享同一进程的资源,如内存、文件描述符等。
- 线程也有自己的私有资源,如栈和线程局部存储。
- 进程间资源是独立的,通常需要 IPC(进程间通信)机制来交换数据。
- 空间:
- 进程拥有独立的地址空间,进程间通信需要特定的 IPC 机制。
- 线程共享进程的地址空间,可以直接访问共享数据,通信更为直接和快速。
5.调试多线程程序
- 使用调试器(如 gdb)时,可以使用
info threads
查看线程列表。 - 使用
thread <thread number>
命令可以切换到特定线程的上下文进行调试。
二.线程设计框架POSIX
1.创建多线程
- 函数原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
thread
:线程 ID,需要实现定义并由函数返回。attr
:线程属性,通常为 NULL,表示默认属性。start_routine
:指向指针函数的函数指针,本质上是一个函数的名称即可,称为回调函数,是线程的执行空间。arg
:回调函数的参数。
- 返回值:成功返回 0,失败返回错误码。
- 注意:1)一次pthread_create执行只能创建一个线程。
2)每个进程至少有一个线程称为主线程。
3)主线程退出则所有创建的子线程都退出。
4)主线程必须有子线程同时运行才算多线程程序。
5)线程id是线程的唯一标识,是CPU维护的一组数字。
6)pstree 查看系统中多线程的对应关系。
7)多个子线程可以执行同一回调函数。
2.当前线程 ID 获取
- 函数原型:
pthread_t pthread_self(void);
- 参数:无。
- 返回值:成功:返回当前线程的 ID,unsigned long int; %lu。失败则返回-1。
3.线程退出
- 自行退出:
void pthread_exit(void *retval);
retval
:线程退出时的返回状态。- 返回值:无。
- 强制退出:
int pthread_cancel(pthread_t thread);
- thread:请求结束一个线程tid。
- 返回值:成功为0,失败为-1。
4.线程资源回收
- 函数原型:
int pthread_join(pthread_t thread, void **retval);
thread
:要回收的子线程的 ID。retval
:子线程的返回值/状态。--->ptread_exit(值);
- 线程回收机制:1)不同于进程,没有孤儿线程和僵尸线程。
2)主线程结束任意生成的子线程都会结束。
3)子线程的结束不会影响到主线程的运行。 - 返回值:成功返回 0,失败返回 -1。
- 子线程的回收策略:1)如果预估子线程可以有限范围内结束则正常用pthread_join等待回收。
2)如果预估子线程可能休眠或者阻塞,则等待一定时间后强制回收。
3)如果子线程已知必须长时间运行则,不再回收其资源。
void *result = NULL;
int status = pthread_join(tid, &result);
if (status == 0 && result != NULL) {
// 使用返回值
printf("Thread returned: %ld\n", (long)result);
}
5.退出回收返回值原理
- 子线程使用
pthread_exit
返回一个void*
类型的指针。 - 主线程通过
pthread_join
获取子线程的返回指针,并访问指向的数据。 - 子线程退出的时候,可以返回一个内存地址,改值所在的内存中可以存储任何数据,只要地址存在,则数据都可以正常返回。
地址类型
- 栈区变量:错误。子线程结束后,其栈帧被销毁,返回的地址不再有效。
- 全局变量:可行。全局变量在整个程序的生命周期内有效,可以直接访问。
- 静态变量:可行。静态变量在函数调用之间保持其值。
- 堆区变量:可行。动态分配的内存在未显式释放前一直有效。
主线程通过一个地址形式的变量来接受子进程
返回的地址变量就可以将该地址中的数据取到。
示例:子线程返回堆区整数
- 目的:从子线程返回一个动态分配的整数。
- 步骤:
- 在子线程中使用
malloc
分配内存并存储整数。 - 使用
pthread_exit
返回指向这块内存的指针。 - 在主线程中使用
pthread_join
获取指针并读取整数。
- 在子线程中使用
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
void* thread_function() {
int* data = (int*)malloc(sizeof(int));
*data = 10; // 将数据存储在堆区
pthread_exit(data); // 返回指向数据的指针
}
int main() {
pthread_t tid;
void** result = NULL; // 注意这里使用 void** 来接收地址
// 创建线程
if (pthread_create(&tid, NULL, thread_function, NULL) != 0) {
perror("Failed to create thread");
return 1;
}
// 等待线程结束并获取返回值
if (pthread_join(tid, (void**)&result) == 0 && result != NULL) {
printf("Thread returned: %d\n", *(int*)(*result));
free(*result); // 释放子线程分配的内存
} else {
perror("Failed to join thread or no result");
}
return 0;
}
三.线程参数传递
使用 pthread_create
传递参数给线程函数。
- 传整数:
//线程函数
void* add(void* arg) {
// 强制转换参数
int a = *((int *)arg);
// 这里可以添加更多的逻辑
return NULL;
}
//线程创建(写到需要的函数体中)
pthread_t tid;
int x = 2, y = 3;
pthread_create(&tid, NULL, add, (void *)&x);
- 传字符:通常作为整数类型传递。
- 传字符串:
- 栈区字符数组:不应直接传递,因为数组会退化成指针。
- 字符串常量:可以直接传递字符串指针。
- 堆区字符串:动态分配内存后传递指针,使用
malloc
分配,记得free
。
//线程函数
void* printString(void* arg) {
char* str = (char *)arg;
printf("%s\n", str);
return NULL;
}
//线程创建
char *str = "Hello, thread!";
char *pc = (char *)malloc(128);//堆区字符串
pthread_create(&tid, NULL, printString, (void *)str);
- 传结构体:1)定义结构体类型。
2)用结构体定义变量。
3)向pthread_create传结构体变量。
4)从fun子线程中获取结构体数据。
//线程函数
void* processStruct(void* arg) {
MyStruct* myData = (MyStruct *)arg;
// 使用 myData->a 和 myData->b
return NULL;
}
//线程创建
MyStruct data = {10, 20};
pthread_create(&tid, NULL, processStruct, (void *)&data);
四.线程属性
1.初始化:
- 结构
:int pthread_attr_init(pthread_attr_t *attr);
- 功能:初始化一个attr的变量。
- 参数:attr,需要变量来接受初始值。
- 返回值:成功为0,失败为非0。
-
detachstate
:设置为PTHREAD_CREATE_DETACHED
。
3.销毁:
- 结构:
int pthread_attr_destroy(pthread_attr_t *attr);
- 功能:销毁attr变量。
- 参数:attr 属性变量。
- 返回值:成功为0,失败为非0。
4.分离属性设置:
- 结构:
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
- 功能:把一个线程设置成相应的属性。
- 参数1:attr 属性变量,有init函数初始化他。
- 参数2:detach state:有两个可选值:
PTHREAD_CREATE_DETACHED:设置分离属性。
第二种设置分离属性:
int pthread_deatch(pthread_t thread);
功能:设置分离属性
参数:线程id号,填自己的id
- 返回值:成功为0,失败为非0。
五.线程清理
1.注册清理函数:
- 结构:
void pthread_cleanup_push(void (*routine)(void *), void *arg);
- 功能:注册一个线程清理函数。
- 参数:routine,线程清理函数的入口
arg,清理函数的参数。 - 返回值:无。
2.调用清理函数:
- 结构:
void pthread_cleanup_pop(int execute);
- 功能:调用线程清理函数。
- 参数:execute:非0 执行清理函数
0 ,不执行清理 - 返回值:无。
六.编译和调试
- 编译时加载库:
-lpthread
第一个方法重启后失效:
alias gcc='gcc -g -pthread '
unalias gcc第二个方法永久起作用:
cd ~ //家目录
vim .bashrc
alias gcc='gcc -g -pthread ' :wqsource .bashrc 生效
- 调试多线程程序时使用
info thread
命令查看线程信息。
七.命令行工具
1.pstree
- 用途:
pstree
显示当前运行的进程树。它以树状图的形式显示进程及其子进程,包括线程。 - 用法:
- 运行
pstree
可以看到所有进程的树状视图。 pstree -p
还会显示进程的 PID。pstree -T
显示线程而不是进程。
- 运行
2.ps
- 用途:
ps
列出当前系统的进程状态。 - 用法:
ps -e
显示所有进程。ps -L
显示轻量级(线程)信息。
1)查看线程相关信息
ps -eLf
:这个命令列出所有进程,并显示每个进程的线程信息。e
表示显示所有进程。L
表示显示线程信息。f
表示完整格式,显示更多详细信息。
- 这个命令会显示进程的 PID、父进程的 PID、线程 ID (
lwp
)、状态 (stat
) 和命令名称 (comm
)。
2)查看特定线程信息
ps -eLo pid,ppid,lwp,stat,comm
:这个命令列出所有进程,并允许你选择性地显示特定列的信息。o
后面跟的是自定义的输出格式,这里指定了要显示的列:进程 ID (pid
)、父进程 ID (ppid
)、线程 ID (lwp
)、状态 (stat
) 和命令名称 (comm
)。