Linux —— 线程控制
- 创建多个线程
- 线程的优缺点
- 优点
- 缺点
- pthread_self
- 进程和线程的关系
- pthread_exit
- 线程等待pthread_ join
- 线程的返回值
- 线程分离
- pthread_detach
- 线程取消
- pthread_cancel
- pthread_t 的理解
我们今天接着来学习线程:
创建多个线程
我们可以结合以前的知识,创建多个线程:
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <vector>
#include <functional>
#include <time.h>
#include <pthread.h>// 使用std::function作为函数包装器类型别名
using func_t = std::function<void()>;// 声明线程池中线程的数量
const int thread_num = 5;// 定义一个结构体来保存线程的相关数据
class ThreadData {
public:// 构造函数初始化线程名称、创建时间和函数执行体ThreadData(const std::string &name, const uint64_t &ctime, func_t f): thread_name(name), create_time(ctime), func(f) {}// 线程名称std::string thread_name;// 创建时间(以Unix时间戳表示)uint64_t create_time;// 线程执行的函数对象func_t func;
};// 线程处理函数,由pthread_create调用
void *Threadhandler(void *args) {// 强制类型转换从void*到ThreadData*ThreadData *td = static_cast<ThreadData *>(args);while (true) {// 打印线程信息并执行函数体std::cout << "new thread thread name: " << td->thread_name << " create time: " << td->create_time << std::endl;td->func();// 模拟工作并让线程休眠1秒sleep(1);}// 注意:此函数理论上不会返回,因为while(true),但C++要求非void*函数有返回值,这里返回nullptr以满足编译要求return nullptr;
}// 示例函数,供线程执行
void Print() {std::cout << "This is a process" << std::endl;
}int main() {// 用于存储所有创建的线程ID的向量std::vector<pthread_t> threads;// 循环创建指定数量的线程for(int i = 0; i < thread_num; i++) {// 生成线程名称std::string name = "thread " + std::to_string(i + 1);pthread_t tid; // 线程ID// 初始化每个线程的数据结构ThreadData *td = new ThreadData(name, (uint64_t)time(nullptr), Print);// 创建线程,传入处理函数和参数pthread_create(&tid, nullptr, Threadhandler, td);// 将创建的线程ID添加到向量中threads.push_back(tid);// 主线程休眠1秒,模拟间隔创建线程的效果sleep(1);}// 打印所有创建的线程IDstd::cout << "thread ids: ";for(const auto &tid: threads) {std::cout << tid << ",";}std::cout << std::endl;// 主线程可以在这里执行其他操作或等待,这里为了简化直接结束// 实际应用中可能需要适当同步机制来管理这些线程的生命周期return 0;
}
我们也可以写段代码,监视我们的线程:
while :; do ps -aL | head -1 && ps -aL | grep mocess; sleep 1; done
线程的优缺点
线程(Threads)是现代操作系统中一种重要的并发执行机制,允许程序内部的多个控制流并发执行。以下是线程的一些主要优缺点:
优点
- 提高资源利用率:线程可以在同一个进程中共享内存和资源,减少了上下文切换的开销,提高了系统的整体效率和资源利用率。
- 响应速度更快:多线程程序可以在等待某个任务(如I/O操作)完成的同时,处理其他任务,使得用户界面更加流畅,响应速度更快。
- 简化编程模型:对于一些复杂的并发任务,使用多线程可以简化编程模型,使程序设计更加直观。
- 灵活性和可扩展性:线程使得程序可以根据需要动态地分配工作,易于扩展以适应不同的负载需求。
- 并行处理:在多处理器或多核系统中,线程可以并行执行,充分利用硬件资源,显著提升程序的执行效率。
缺点
- 资源共享和数据一致性问题:多个线程访问共享资源可能导致竞态条件、死锁和资源争用等问题,需要复杂的同步机制(如互斥锁、信号量等)来保证数据的一致性,这会增加编程复杂度。
- 上下文切换开销:尽管线程间上下文切换比进程快,但频繁的线程切换仍然会消耗CPU时间,降低性能。
- 内存和资源占用:每个线程都会占用一定的内存空间(如栈空间),大量线程会导致内存消耗增加,特别是在内存受限的环境中。
- 调试困难:多线程程序的调试相对单线程程序更为复杂,因为问题可能与线程的执行顺序有关,难以复现和定位错误。
- 死锁和活锁:不当的线程同步可能导致死锁,即两个或更多的线程互相等待对方释放资源而无法继续执行。此外,活锁也是可能的问题,线程持续进行无意义的操作等待某种条件发生,但实际上条件永远无法达成。
同时,线程的健壮性并不是很优秀:
线程的健壮性相比多进程来说通常被认为较低。这是因为线程共享同一进程的内存空间和资源,这种资源共享的特性带来了以下几点关于健壮性的影响:
- 资源共享风险:线程之间可以直接访问共享内存,包括全局变量和其他静态数据,这可能导致数据竞争和竞态条件。如果不采取合适的同步措施(如互斥锁、信号量等),一个线程对共享数据的修改可能会干扰其他线程,从而引发不可预测的行为,甚至程序崩溃。
- 异常传播:在某些情况下,一个线程的异常终止(如 segmentation fault)可能会直接影响到整个进程,导致所有线程一起终止。这是因为所有线程共享同一地址空间,一个线程的错误操作可能破坏其他线程正在使用的数据结构或资源。
- 线程安全问题:编写线程安全的代码需要额外的努力,比如正确管理锁的使用、避免死锁和活锁等,这增加了开发的复杂度。如果线程间的交互没有正确处理,很容易引入难以发现和修复的错误。
- 调试挑战:多线程程序的调试比单线程程序更为复杂。由于线程执行的并发性和不确定性,错误可能不会稳定复现,且问题的原因可能隐藏在复杂的线程交互中,这使得调试和故障排查变得困难。
我们举个例子,我们故意触发异常:
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <vector>
#include <functional>
#include <time.h>
#include <pthread.h>// 使用std::function作为函数包装器类型别名
using func_t = std::function<void()>;// 声明线程池中线程的数量
const int thread_num = 5;// 定义一个结构体来保存线程的相关数据
class ThreadData {
public:// 构造函数初始化线程名称、创建时间和函数执行体ThreadData(const std::string &name, const uint64_t &ctime, func_t f): thread_name(name), create_time(ctime), func(f) {}// 线程名称std::string thread_name;// 创建时间(以Unix时间戳表示)uint64_t create_time;// 线程执行的函数对象func_t func;
};// 线程处理函数,由pthread_create调用
void *Threadhandler(void *args) {int a = 10;// 强制类型转换从void*到ThreadData*ThreadData *td = static_cast<ThreadData *>(args);while (true) {// 打印线程信息并执行函数体std::cout << "new thread thread name: " << td->thread_name << " create time: " << td->create_time << std::endl;td->func();if(td->thread_name == "thread 1"){std::cout << td->thread_name << " alarm !!!!!" << std::endl;a /= 0; // 故意制作异常}// 模拟工作并让线程休眠1秒sleep(1);}// 注意:此函数理论上不会返回,因为while(true),但C++要求非void*函数有返回值,这里返回nullptr以满足编译要求return nullptr;
}// 示例函数,供线程执行
void Print() {std::cout << "This is a process" << std::endl;
}int main() {// 用于存储所有创建的线程ID的向量std::vector<pthread_t> threads;// 循环创建指定数量的线程for(int i = 0; i < thread_num; i++) {// 生成线程名称std::string name = "thread " + std::to_string(i + 1);pthread_t tid; // 线程ID// 初始化每个线程的数据结构ThreadData *td = new ThreadData(name, (uint64_t)time(nullptr), Print);// 创建线程,传入处理函数和参数pthread_create(&tid, nullptr, Threadhandler, td);// 将创建的线程ID添加到向量中threads.push_back(tid);// 主线程休眠1秒,模拟间隔创建线程的效果sleep(1);}// 打印所有创建的线程IDstd::cout << "thread ids: ";for(const auto &tid: threads) {std::cout << tid << ",";}std::cout << std::endl;// 主线程可以在这里执行其他操作或等待,这里为了简化直接结束// 实际应用中可能需要适当同步机制来管理这些线程的生命周期return 0;
}
我们这里1号线程触发异常,整个进程直接挂掉。
因此,虽然多线程可以提高程序的效率和响应性,但其健壮性依赖于开发者对并发控制和资源管理的精细设计。实践中,通常推荐使用高级并发工具、遵循最佳实践,并进行充分的测试来确保线程安全和提高程序的健壮性。
pthread_self
pthread_self可以获取自身的线程id:
进程和线程的关系
一般来说,线程和进程的关系是,牵一发而动全身,比如:一个线程退出,不能直接exit退出:
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <vector>
#include <functional>
#include <time.h>
#include <pthread.h>// 使用std::function作为函数包装器类型别名
using func_t = std::function<void()>;// 声明线程池中线程的数量
const int thread_num = 5;// 定义一个结构体来保存线程的相关数据
class ThreadData
{
public:// 构造函数初始化线程名称、创建时间和函数执行体ThreadData(const std::string &name, const uint64_t &ctime, func_t f): thread_name(name), create_time(ctime), func(f) {}// 线程名称std::string thread_name;// 创建时间(以Unix时间戳表示)uint64_t create_time;// 线程执行的函数对象func_t func;
};// 线程处理函数,由pthread_create调用
void *Threadhandler(void *args)
{int a = 10;// 强制类型转换从void*到ThreadData*ThreadData *td = static_cast<ThreadData *>(args);// 打印线程信息并执行函数体std::cout << "new thread thread name: " << td->thread_name << " create time: " << td->create_time << std::endl;td->func();// if(td->thread_name == "thread 1")// {// std::cout << td->thread_name << " alarm !!!!!" << std::endl;// a /= 0; // 故意制作异常// }// 模拟工作并让线程休眠1秒sleep(1);// 注意:此函数理论上不会返回,因为while(true),但C++要求非void*函数有返回值,这里返回nullptr以满足编译要求exit(0); //exit返回//return nullptr;
}// 示例函数,供线程执行
void Print()
{std::cout << "This is a process" << std::endl;
}int main() {// 用于存储所有创建的线程ID的向量std::vector<pthread_t> threads;// 循环创建指定数量的线程for(int i = 0; i < thread_num; i++) {// 生成线程名称std::string name = "thread " + std::to_string(i + 1);pthread_t tid; // 线程ID// 初始化每个线程的数据结构ThreadData *td = new ThreadData(name, (uint64_t)time(nullptr), Print);// 创建线程,传入处理函数和参数pthread_create(&tid, nullptr, Threadhandler, td);// 将创建的线程ID添加到向量中threads.push_back(tid);// 主线程休眠1秒,模拟间隔创建线程的效果sleep(1);}// 打印所有创建的线程IDstd::cout << "thread ids: ";for(const auto &tid: threads) {std::cout << tid << ",";}std::cout << std::endl;// 主线程可以在这里执行其他操作或等待,这里为了简化直接结束// 实际应用中可能需要适当同步机制来管理这些线程的生命周期return 0;
}
我们这里创建了5个线程,但是线程1 exit会直接导致整个进程退出,所以,如何优雅的实现线程的退出呢?一般返回nullptr即可:
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <vector>
#include <functional>
#include <time.h>
#include <pthread.h>// 使用std::function作为函数包装器类型别名
using func_t = std::function<void()>;// 声明线程池中线程的数量
const int thread_num = 5;// 定义一个结构体来保存线程的相关数据
class ThreadData
{
public:// 构造函数初始化线程名称、创建时间和函数执行体ThreadData(const std::string &name, const uint64_t &ctime, func_t f): thread_name(name), create_time(ctime), func(f) {}// 线程名称std::string thread_name;// 创建时间(以Unix时间戳表示)uint64_t create_time;// 线程执行的函数对象func_t func;
};// 线程处理函数,由pthread_create调用
void *Threadhandler(void *args)
{int a = 10;// 强制类型转换从void*到ThreadData*ThreadData *td = static_cast<ThreadData *>(args);// 打印线程信息并执行函数体std::cout << "new thread thread name: " << td->thread_name << " create time: " << td->create_time << std::endl;td->func();// if(td->thread_name == "thread 1")// {// std::cout << td->thread_name << " alarm !!!!!" << std::endl;// a /= 0; // 故意制作异常// }// 模拟工作并让线程休眠1秒sleep(1);// 注意:此函数理论上不会返回,因为while(true),但C++要求非void*函数有返回值,这里返回nullptr以满足编译要求//exit(0); //exit返回return nullptr;
}// 示例函数,供线程执行
void Print()
{std::cout << "This is a process" << std::endl;
}int main() {// 用于存储所有创建的线程ID的向量std::vector<pthread_t> threads;// 循环创建指定数量的线程for(int i = 0; i < thread_num; i++) {// 生成线程名称std::string name = "thread " + std::to_string(i + 1);pthread_t tid; // 线程ID// 初始化每个线程的数据结构ThreadData *td = new ThreadData(name, (uint64_t)time(nullptr), Print);// 创建线程,传入处理函数和参数pthread_create(&tid, nullptr, Threadhandler, td);// 将创建的线程ID添加到向量中threads.push_back(tid);// 主线程休眠1秒,模拟间隔创建线程的效果sleep(1);}// 打印所有创建的线程IDstd::cout << "thread ids: ";for(const auto &tid: threads) {std::cout << tid << ",";}std::cout << std::endl;// 主线程可以在这里执行其他操作或等待,这里为了简化直接结束// 实际应用中可能需要适当同步机制来管理这些线程的生命周期return 0;
}
同时,我们还可以用pthread_exit:
pthread_exit
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <vector>
#include <functional>
#include <time.h>
#include <pthread.h>// 使用std::function作为函数包装器类型别名
using func_t = std::function<void()>;// 声明线程池中线程的数量
const int thread_num = 5;// 定义一个结构体来保存线程的相关数据
class ThreadData
{
public:// 构造函数初始化线程名称、创建时间和函数执行体ThreadData(const std::string &name, const uint64_t &ctime, func_t f): thread_name(name), create_time(ctime), func(f) {}// 线程名称std::string thread_name;// 创建时间(以Unix时间戳表示)uint64_t create_time;// 线程执行的函数对象func_t func;
};// 线程处理函数,由pthread_create调用
void *Threadhandler(void *args)
{int a = 10;// 强制类型转换从void*到ThreadData*ThreadData *td = static_cast<ThreadData *>(args);// 打印线程信息并执行函数体std::cout << "new thread thread name: " << td->thread_name << " create time: " << td->create_time << std::endl;td->func();// if(td->thread_name == "thread 1")// {// std::cout << td->thread_name << " alarm !!!!!" << std::endl;// a /= 0; // 故意制作异常// }// 模拟工作并让线程休眠1秒sleep(1);// 注意:此函数理论上不会返回,因为while(true),但C++要求非void*函数有返回值,这里返回nullptr以满足编译要求//exit(0); //exit返回//return nullptr;pthread_exit(nullptr);
}// 示例函数,供线程执行
void Print()
{std::cout << "This is a process" << std::endl;
}int main() {// 用于存储所有创建的线程ID的向量std::vector<pthread_t> threads;// 循环创建指定数量的线程for(int i = 0; i < thread_num; i++) {// 生成线程名称std::string name = "thread " + std::to_string(i + 1);pthread_t tid; // 线程ID// 初始化每个线程的数据结构ThreadData *td = new ThreadData(name, (uint64_t)time(nullptr), Print);// 创建线程,传入处理函数和参数pthread_create(&tid, nullptr, Threadhandler, td);// 将创建的线程ID添加到向量中threads.push_back(tid);// 主线程休眠1秒,模拟间隔创建线程的效果sleep(1);}// 打印所有创建的线程IDstd::cout << "thread ids: ";for(const auto &tid: threads) {std::cout << tid << ",";}std::cout << std::endl;// 主线程可以在这里执行其他操作或等待,这里为了简化直接结束// 实际应用中可能需要适当同步机制来管理这些线程的生命周期return 0;
}
线程等待pthread_ join
线程既然是进程的迷你版,肯定也会有跟进程相关的地方,比如我们的线程退出时也是需要被等待的,而我们所用的接口就是就是pthread_join:
我们来举个例子:
#include <iostream> // 标准输入输出库
#include <unistd.h> // 定义了 usleep 函数,用于延迟线程执行
#include <cstring> // 字符串操作函数库
#include <vector> // 动态数组容器
#include <functional> // 函数对象包装器库
#include <time.h> // 时间相关函数库
#include <pthread.h> // POSIX 线程库// 线程处理函数
void *Threadhandler(void *args)
{usleep(1000); // 线程启动后暂停1毫秒// 将传入的void指针转换为std::string类型,作为线程名称std::string name = static_cast<const char*>(args);int cnt = 5; // 循环计数器// 输出线程信息并等待一秒,循环5次while(cnt--){std::cout << "I am a Light process My LWP is :" << pthread_self() << std::endl; // 打印当前线程的轻量级进程ID(LWP)sleep(1); // 线程休眠1秒}return nullptr; // 线程结束,返回空指针
}int main()
{pthread_t tid; // 定义线程ID变量// 创建新线程,传入线程处理函数、线程属性(nullptr表示使用默认属性)、入口参数和线程ID的地址pthread_create(&tid, nullptr, Threadhandler, (void *)"thread - 1");std::cout << "I am the main Light process My LWP is :" << pthread_self() << std::endl; // 打印主线程的LWPsleep(10); // 主线程休眠10秒,保证子线程有足够时间运行// 等待子线程tid结束,成功返回0,失败返回非零值。第二个参数接收线程的返回值,这里不需要所以传入nullptrint n = pthread_join(tid, nullptr);std::cout << "return value is :" << n << std::endl; // 打印pthread_join的返回值,表示是否成功加入线程return 0; // 主程序结束
}
这里注意一下,如果线程退出并没有被等待,会导致类似像僵尸进程这样的问题,但是这个不怎么容易观察。
线程的返回值
我们来看看pthread_join的手册:
后面这个retval这个参数,在线程退出的时候,会把退出的结果放到retval中,意思就是这个retval是一个输出型参数,输入之后会把线程的退出结果带出来:
我们可以试一试:
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <vector>
#include <functional>
#include <time.h>
#include <pthread.h>void *Threadhandler(void *args)
{usleep(1000);std::string name = static_cast<const char*>(args);int cnt = 5;while(cnt--){std::cout << "I am a Light process My LWP is :" << pthread_self() << std::endl;sleep(1);}return (void *)"thread-1";
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,Threadhandler,(void *)"thread - 1");std::cout << "I am the main Light process My LWP is :" << pthread_self() << std::endl;void *ret = nullptr;int n = pthread_join(tid,&ret);std::cout << "return value is :" << (const char*)ret << std::endl;return 0;
}
我们这里返回的是一个字符串,我们看看我们能否打印的出来:
这里用pthread_exit也是可以的。
这里既然是void *说明我们是可以传递任何类型,我们也可以传递一个结构体回去:
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <string>// 自定义线程返回结构体,用于存储线程退出时的相关信息
class ThreadReturn
{
public:// 构造函数,初始化线程ID、信息和退出码ThreadReturn(pthread_t id, std::string info, int code): _id(id), // 线程ID_info(info), // 线程退出时的信息_code(code) // 线程退出码{}// 数据成员pthread_t _id; // 线程的ID,在线程退出时记录std::string _info; // 线程退出时的描述信息int _code; // 线程退出的状态码
};// 线程处理函数
void *Threadhandler(void *args)
{usleep(1000); // 暂停1毫秒std::string name = static_cast<const char*>(args); // 获取线程名称int cnt = 5; // 循环计数器// 循环输出线程信息并休眠,模拟工作过程while(cnt--){std::cout << "I am a Light process My LWP is :" << pthread_self() << std::endl;sleep(1);}// 线程处理完毕,创建ThreadReturn实例以传递退出信息ThreadReturn* ret = new ThreadReturn(pthread_self(), name, 10);return ret; // 将ThreadReturn对象的地址作为线程的返回值
}int main()
{pthread_t tid; // 主线程中定义线程ID// 创建新线程,传入处理函数、线程属性、参数和线程ID指针pthread_create(&tid, nullptr, Threadhandler, (void *)"thread - 1");std::cout << "I am the main Light process My LWP is :" << pthread_self() << std::endl; // 输出主线程IDvoid *ret = nullptr; // 定义一个void指针来接收线程的返回值int n = pthread_join(tid, &ret); // 等待线程tid结束,并获取其返回值到ret指针// 将ret指针转换为ThreadReturn对象指针,以便访问其中的数据ThreadReturn* r = static_cast<ThreadReturn*>(ret);if(r != nullptr) { // 确保转换成功std::cout << "return value is :" << "id :" << r->_id << std::endl;std::cout << "return value is :" << "info :" << r->_info << std::endl;std::cout << "return value is :" << "code :" << r->_code << std::endl;delete r; // 释放分配的内存}return 0; // 主程序结束
}
线程分离
我们之前通过实验看到了线程和线程之间的关联,线程退出之后要进行回收。
但其实,如果我们的主线程只想完成自己的任务,而并不想管其他的线程可不可以呢?答案是可以的,我们可以进行线程分离,使之主线程不管其他线程的死活:
pthread_detach
pthread_detach()
是POSIX线程库中的一个函数,用于改变指定线程的分离状态。当一个线程被“分离”(detached)时,它会在执行结束后自动被系统回收资源,而不需要其他线程调用pthread_join()
来显式等待它结束。这对于那些不需要收集线程返回值或者不需要精确控制线程结束时间的场景非常有用。
函数原型如下:
int pthread_detach(pthread_t thread);
- 参数:
thread
:要分离的线程的标识符(ID),即之前通过pthread_create()
创建线程时返回的值。
- 返回值:
- 成功时返回0。
- 失败时返回非零的错误码。
功能:
- 如果调用成功,指定的线程将在终止时自动释放其资源,包括栈和线程描述符,而不需要其他线程的进一步干预。
- 分离状态的线程不能被其他线程通过
pthread_join()
来等待和回收资源。
使用场景:
- 当线程执行的任务是独立的,不需要与其他线程同步或交换数据时。
- 当你不需要关心线程的具体结束状态或返回值,只关心它执行完成即可。
注意事项:
- 一旦线程被分离,就不能再通过
pthread_join()
来等待它。- 如果你既想在线程结束时执行某些清理工作,又想让它自动回收资源,可以在线程函数的最后手动执行清理操作,然后再调用
pthread_exit()
显式终止线程,这样分离后也能确保资源被正确回收。- 调用
pthread_detach()
前应确保线程还在运行,不要对尚未创建或已经终止的线程调用此函数。
void *Threadhandler(void *args)
{usleep(1000);std::string name = static_cast<const char*>(args);std::cout << "I am a Light process My LWP is :" << pthread_self() << std::endl;return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,Threadhandler,(void *)"thread - 1");pthread_detach(tid);std::cout << "I am the main Light process My LWP is :" << pthread_self() << std::endl;void *ret = nullptr;int n = pthread_join(tid,&ret);std::cout << "return value is :" << "code :"<< (long long int)n << std::endl;return 0;
}
我们的错误码设置为了22,表示一个无效的输入:
错误码22通常对应于EINVAL错误,即无效参数(Invalid argument)。这意味着传递给pthread_join的线程ID无效,或者线程已经终止并且不是加入(joinable)状态。由于线程已经被显式分离(通过pthread_detach(pthread_self())),它不再是可加入状态,因此尝试用pthread_join来等待这个线程就会收到EINVAL错误。
线程取消
pthread_cancel
pthread_cancel()
是POSIX线程库中的一个函数,用于请求取消(cancellation)指定的线程。这意味着请求线程(调用pthread_cancel
的线程)向目标线程发送一个取消请求,目标线程在接收到这个请求后,根据其取消状态和取消类型,可以选择立即终止或在某个合适的时机终止执行。
函数原型如下:
int pthread_cancel(pthread_t thread);
- 参数:
thread
:要被取消的线程的标识符(ID),即之前通过pthread_create()
创建线程时返回的值。
- 返回值:
- 成功时返回0。
- 失败时返回非零的错误码,如
ESRCH
(没有这样的线程)。
功能:
- 发送一个取消请求给指定的线程。目标线程是否响应这个请求取决于其取消状态和取消类型。
- 线程默认是不响应取消的(即取消点的插入是可选的),这意味着仅发出取消请求并不会立即停止线程,除非线程在某个取消点上主动检查取消请求状态。
- 为了使线程能够响应取消,开发者需要在代码中适当的位置插入取消点,通常是通过调用某些库函数(如
sleep()
,pthread_testcancel()
)自动完成的,或者显式地调用pthread_testcancel()
。
使用场景:
- 当需要基于外部条件(如用户中断、错误处理等)提前结束线程的执行时。
- 在长任务中提供取消机制,增强程序的灵活性和响应性。
注意事项:
- 线程可以在接到取消请求后执行清理工作,通过设置取消类型(
PTHREAD_CANCEL_DEFERRED
或PTHREAD_CANCEL_ASYNCHRONOUS
)来控制响应方式。- 即使取消请求被发送,线程也可能不会立即终止,除非它正在执行取消点或已经设置了立即响应取消的类型。
- 取消线程应谨慎使用,特别是当线程持有锁或资源时,突然取消可能导致资源泄露或其他一致性问题,需要确保资源正确清理。
- 成功发送取消请求并不意味着线程已经终止,需要通过其他机制(如共享变量、条件变量等)来确认线程是否已响应取消并完成清理。
比如我们可以这样:
void *Threadhandler(void *args)
{usleep(1000);std::string name = static_cast<const char*>(args);int cnt = 5;while(cnt--){std::cout << "I am a Light process My LWP is :" << pthread_self() << std::endl;sleep(1);}//pthread_detach(pthread_self());return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,Threadhandler,(void *)"thread - 1");std::cout << "I am the main Light process My LWP is :" << pthread_self() << std::endl;//pthread_detach(tid);int n = pthread_cancel(tid);std::cout << "cancel value :" << strerror((int64_t)n) << std::endl;void *ret = nullptr;n = pthread_join(tid,&ret);std::cout << "return value is :" << "code :"<< strerror((long long int)n) << std::endl;return 0;
}
我们看到取消和等待都是成功了的,但是如果我们线程分离了呢?
void *Threadhandler(void *args)
{usleep(1000);std::string name = static_cast<const char*>(args);int cnt = 5;while(cnt--){std::cout << "I am a Light process My LWP is :" << pthread_self() << std::endl;sleep(1);}pthread_detach(pthread_self());return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,Threadhandler,(void *)"thread - 1");std::cout << "I am the main Light process My LWP is :" << pthread_self() << std::endl;pthread_detach(tid);int n = pthread_cancel(tid);std::cout << "cancel value :" << strerror((int64_t)n) << std::endl;void *ret = nullptr;n = pthread_join(tid,&ret);std::cout << "return value is :" << "code :"<< strerror((long long int)n) << std::endl;return 0;
}
我们发现,线程分离之后,可以完成线程取消,但是不能完成线程等待。
pthread_t 的理解
我们之前打印过pthread_t 的编号:
这串数字换成十六进制,其实就是一个地址,每一个线程都有自己的地址。
那么这个地址到底是什么呢?
我们首先知道,Linux在系统上并没有提供关于线程的接口,管理线程的是它原生的pthread库:
那么,我们每创建一个线程,库都要组织管理它,所以最后库中就会存储的有每个线程的结构地址:
而我们打印的那一大串数字就是每个struct_pthread 的地址。