深入理解Linux中的线程控制:多线程编程的实战技巧
个人主页:chian-ocean
文章专栏-Linux
前言:
POSIX线程(Pthreads) 是一种在 POSIX 标准下定义的线程库,它为多线程编程提供了统一的接口,主要用于 UNIX 和类 UNIX 系统(如 Linux、MacOS 和 BSD 等)。POSIX 线程(Pthreads)允许程序在多个处理器上并行运行,从而提高应用程序的性能,尤其在多核处理器环境中。
监控线程的bash
while :; do ps -aL | head -1 ; ps -aL | grep thread ; sleep 1;done
线程的控制
- 与线程有关的函数构成了⼀个完整的系列,绝⼤多数函数的名字都是以
pthread_
打头的 - 要使⽤这些函数库,要通过引⼊头⽂
pthread.h
- 链接这些线程函数库时要使⽤编译器命令的“-lpthread”选项
线程的创建(pthread_create
)
#include int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine)(void *), void *arg);
参数解析:
pthread_t *thread
:指向pthread_t
类型的变量,这个变量将存储新线程的 ID。const pthread_attr_t *attr
:指向pthread_attr_t
类型的指针,它包含了新线程的属性(如栈大小、调度策略等)。如果传入NULL
,则使用默认属性。void *(*start_routine)(void *)
:这是一个指向线程执行函数的指针,该函数接收一个void*
类型的参数,并返回void*
类型的结果。void *arg
:这是传递给start_routine
函数的参数,允许你向线程传递数据。
示例:
#include #include #include using namespace std; // 线程函数void* mythread(void* args){ // 创建一个循环,子线程会打印 6 次 for(int i = 0; i < 6; i++) { sleep(1); // 让线程睡眠 1 秒钟,模拟任务的执行 cout << \"Child Thread\" << endl; // 打印“Child Thread”,表示子线程在运行 } return nullptr;}// 主函数int main(){ pthread_t tid; // 定义一个线程ID变量 // 创建子线程,线程的执行函数是 mythread,其他参数为默认值 pthread_create(&tid, nullptr, mythread, nullptr); // 主线程执行,循环 6 次 for(int i = 0; i < 6; i++) { cout << \"Main Thread\" << endl; // 打印“Main Thread”,表示主线程在运行 sleep(1); // 让主线程睡眠 1 秒钟 } // 等待子线程完成执行 pthread_join(tid, nullptr); // 阻塞等待 tid 线程执行完毕 return 0; // 程序正常退出}
pthread_create()
:用于创建一个新的线程,mythread
是新线程的执行函数。该函数接受参数nullptr
,表示没有传递任何参数给线程。pthread_join(tid, nullptr)
:等待线程tid
执行完成,pthread_join
阻塞主线程,直到子线程执行完毕。
打印结果:
线程退出
pthread_join
#include int pthread_join(pthread_t thread, void **retval);
参数说明:
pthread_t thread
:要等待结束的线程 ID。void **retval
:用于存储线程返回值的指针。如果不需要返回值,可以将其设置为NULL
。
功能描述:
pthread_join()
函数使得调用该函数的线程(通常是主线程)阻塞,直到指定的线程(由thread
参数指定)执行完毕。- 当线程结束后,系统会回收该线程的资源。如果
retval
不为NULL
,线程的返回值将被存储在retval
指向的位置。
示例:
#include#include#includeusing namespace std;// 子线程的执行函数void* mythread(void*args){ // 循环6次,每次打印一次信息,并且每次暂停1秒 for(int i = 0 ; i < 6 ;i++) { sleep(1); // 休眠1秒钟 cout << \"Child Thread is running ....\" << endl; // 输出子线程运行的提示 } // 返回一个值100,并强制转换为 void* 类型 return (void*)100;}int main(){ pthread_t tid; // 定义一个线程ID变量 // 创建子线程,传入 mythread 函数作为线程执行的函数,参数为 nullptr pthread_create(&tid, nullptr, mythread, nullptr); // 主线程运行6次,每次输出一次信息,并且每次暂停1秒 for(int i = 0 ; i < 6 ;i++) { cout << \"Main Thread is running ..\" << endl; // 输出主线程运行的提示 sleep(1); // 休眠1秒钟 } void* retval; // 声明一个指向 void 的指针,用于接收子线程的返回值 // 等待子线程结束,并将子线程的返回值存储到 retval 中 pthread_join(tid, &retval); // 输出子线程的返回值,将其强制转换为整数类型并输出 cout << (int64_t)retval << endl; // 输出子线程的返回值,转换为整数 return 0;}
程序流程:
- 并发输出:主线程和子线程的输出交替进行,打印在终端上时会交替显示 “Main Thread is running …” 和 “Child Thread is running …”。
- 同步:主线程通过
pthread_join()
等待子线程完成,并获取子线程的返回值。 - 线程返回值:子线程通过
return (void*)100
返回一个void*
类型的值,主线程通过pthread_join()
捕获该返回值并输出。
打印:
pthread_exit
#include void pthread_exit(void *retval);
参数说明:
retval
:这是一个指针,线程退出时可以返回的值。该值可以被其他线程通过pthread_join
获取,用来传递线程的退出状态或其他信息。- 注意:
retval
可以是任何类型的指针,通常是一个线程退出的状态信息。
示例:
#include#include#includeusing namespace std;void* mythread(void* args) // 线程函数{ // 子线程执行的操作,循环输出 \"Child Thread is running ....\" for (int i = 0; i < 6; i++) { sleep(1); // 每次休眠1秒 cout << \"Child Thread is running ....\" << endl; } // 输出结束前的信息 cout << \"Child Pthread_exit\" << endl; // 使用 pthread_exit 显式退出线程并返回一个状态码 100 pthread_exit((void*)100); // 这一行代码不会被执行到,因为 pthread_exit 已经退出线程 return (void*)100;}int main() { pthread_t tid; // 声明一个线程标识符 // 创建线程 pthread_create(&tid, nullptr, mythread, nullptr); // 主线程执行的操作 for (int i = 0; i < 6; i++) { cout << \"Main Thread is running ..\" << endl; sleep(1); // 每次休眠1秒 } // 主线程休眠,确保子线程有时间执行 sleep(8); // 等待子线程执行完毕 // 主线程结束前输出信息 cout << \"main return \" << endl; return 0; // 程序结束}
程序流程:
- 程序运行时,子线程和主线程会交替输出 “Child Thread is running …” 和 “Main Thread is running …”。
- 主线程执行完成它的循环后,调用
sleep(8)
来等待子线程的执行。此时,子线程已经执行完它的循环,并通过pthread_exit
显式退出。 - 由于没有在主线程中调用
pthread_join()
,主线程并没有等待子线程完成,它只是通过sleep(8)
暂停一段时间,确保子线程有足够的时间结束。
打印:
pthread_cancal
#include int pthread_cancel(pthread_t thread);
参数说明:
pthread_t thread
:目标线程的线程 ID(TID),即你希望取消的线程。
功能描述:
pthread_cancel
用于向指定的线程发送取消请求。调用此函数后,目标线程会接收到取消请求,并在合适的时机响应取消请求。
返回值:
- 返回 0 表示成功,其他返回值表示失败,通常是因为无法取消线程(如线程已经结束等)。
示例:
#include#include#include#include#includeusing namespace std;// Hex函数,用于将整数转换为十六进制字符串string Hex(int data){ char buff[1034] = {0}; // snprintf用于将整数data转换为十六进制字符串 snprintf(buff,sizeof(buff),\"0x%x\",data); return buff;}// 线程函数,打印每个线程的整数值void* mythread(void* args){ // 将传入的参数指针转换为整型指针 int* i = (int*)args; while(true) // 无限循环,模拟线程的持续运行 { sleep(1); // 每次休眠1秒 cout << \"thread: \" << *i << endl; // 输出线程ID(即传递给线程的值) } return (void*)100; // 返回一个指针类型的值,通常线程退出时返回状态}int main(){ // 定义一个 vector 来存储线程ID vector<pthread_t> th; // 创建4个线程 for(int i = 0; i < 4; i++) { pthread_t tid; // 定义一个线程ID变量 // 创建线程,将线程ID、线程函数(mythread)以及传递给线程的参数(i的地址)传入 pthread_create(&tid, nullptr, mythread, &i); // 将线程ID添加到线程容器中 th.push_back(tid); } cout << \"ready calcel\" << endl; sleep(3); // 休眠3秒,等待线程输出 // 取消每个创建的线程 for(int i = 0; i < th.size(); i++) { cout <<\"calcel: thread \" << i <<endl; // 输出正在取消的线程编号 pthread_cancel(th[i]); // 取消对应的线程 } sleep(1); // 稍等1秒,确保线程能够响应取消请求 cout << \"main return \"<<endl; // 输出主线程返回信息 return 0; // 程序结束}
程序流程:
-
** 主线程创建子线程:**主线程使用
pthread_create()
创建 4 个子线程,每个子线程打印传递给它的整数值(即循环中的i
),并在每秒打印一次。 -
**线程输出:**每个线程在无限循环中每秒输出
thread:
,其中是它收到的整数(传递给线程的
i
)。 -
**主线程等待:**主线程在创建完线程后,休眠 3 秒,允许线程输出信息。
-
**取消线程:**主线程在 3 秒后调用
pthread_cancel()
来取消所有 4 个线程,每个线程被取消后,它会终止执行(线程的退出取决于它们在什么地方被取消)。 -
**程序结束:**主线程输出
\"main return\"
并结束,程序执行完毕。
打印:
线程分离
#include int pthread_detach(pthread_t thread);
参数:
thread
:要设置为分离状态的线程 ID。
功能描述
- 功能描述:
pthread_detach
用于将一个线程设置为 分离状态。当线程被设置为分离状态后,线程在完成执行时会自动释放资源,而不需要其他线程显式地调用pthread_join()
来清理线程资源。
示例:
#include#include#include#include#includeusing namespace std;// Hex函数:将整数转换为十六进制字符串string Hex(int data){ char buff[1034] = {0}; snprintf(buff, sizeof(buff), \"0x%x\", data); // 将整数以十六进制格式转换成字符串 return buff;}// 线程函数void* mythread(void* args){ pthread_detach(pthread_self()); // 将当前线程设置为分离状态 int* i = (int*)args; // 将传入的参数指针转换为整型指针 int n = 3; // 循环打印线程编号(传递给线程的值),共打印3次 while(n--) { sleep(1); // 每次循环休眠1秒 cout << \"thread: \" << *i << endl; // 输出线程的编号 } return (void*)100; // 线程返回值}int main(){ vector<pthread_t> th; // 用于存储线程ID的容器 cout << \"线程的创建\" << endl; // 创建4个线程 for(int i = 0; i < 4; i++) { pthread_t tid; pthread_create(&tid, nullptr, mythread, &i); // 创建线程并传递 i 的地址作为参数 th.push_back(tid); // 将创建的线程ID加入到线程容器中 } sleep(10); // 主线程休眠10秒,确保所有子线程能运行完 cout << \"main return \" << endl; return 0; // 主程序结束}
程序执行流程:
- 主线程通过
pthread_create
创建 4 个子线程,每个子线程都会执行mythread
函数。 - 每个子线程会打印 3 次其编号(线程的参数),并在每次打印后休眠 1 秒。
- 主线程休眠 10 秒,以确保子线程有足够的时间打印信息。
- 每个子线程在完成执行后会自动退出并释放资源,因为它们是分离线程(调用了
pthread_detach
)。 - 主线程输出 “main return” 并结束。
打印:
- 以上的代码没有实现同步,可能会导致错乱打印。