Linux —— 线程控制

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)是现代操作系统中一种重要的并发执行机制,允许程序内部的多个控制流并发执行。以下是线程的一些主要优缺点:

优点

  1. 提高资源利用率:线程可以在同一个进程中共享内存和资源,减少了上下文切换的开销,提高了系统的整体效率和资源利用率。
  2. 响应速度更快:多线程程序可以在等待某个任务(如I/O操作)完成的同时,处理其他任务,使得用户界面更加流畅,响应速度更快。
  3. 简化编程模型:对于一些复杂的并发任务,使用多线程可以简化编程模型,使程序设计更加直观。
  4. 灵活性和可扩展性:线程使得程序可以根据需要动态地分配工作,易于扩展以适应不同的负载需求。
  5. 并行处理:在多处理器或多核系统中,线程可以并行执行,充分利用硬件资源,显著提升程序的执行效率。

缺点

  1. 资源共享和数据一致性问题:多个线程访问共享资源可能导致竞态条件、死锁和资源争用等问题,需要复杂的同步机制(如互斥锁、信号量等)来保证数据的一致性,这会增加编程复杂度。
  2. 上下文切换开销:尽管线程间上下文切换比进程快,但频繁的线程切换仍然会消耗CPU时间,降低性能。
  3. 内存和资源占用:每个线程都会占用一定的内存空间(如栈空间),大量线程会导致内存消耗增加,特别是在内存受限的环境中。
  4. 调试困难:多线程程序的调试相对单线程程序更为复杂,因为问题可能与线程的执行顺序有关,难以复现和定位错误。
  5. 死锁和活锁:不当的线程同步可能导致死锁,即两个或更多的线程互相等待对方释放资源而无法继续执行。此外,活锁也是可能的问题,线程持续进行无意义的操作等待某种条件发生,但实际上条件永远无法达成。

同时,线程的健壮性并不是很优秀:

线程的健壮性相比多进程来说通常被认为较低。这是因为线程共享同一进程的内存空间和资源,这种资源共享的特性带来了以下几点关于健壮性的影响:

  1. 资源共享风险:线程之间可以直接访问共享内存,包括全局变量和其他静态数据,这可能导致数据竞争和竞态条件。如果不采取合适的同步措施(如互斥锁、信号量等),一个线程对共享数据的修改可能会干扰其他线程,从而引发不可预测的行为,甚至程序崩溃。
  2. 异常传播:在某些情况下,一个线程的异常终止(如 segmentation fault)可能会直接影响到整个进程,导致所有线程一起终止。这是因为所有线程共享同一地址空间,一个线程的错误操作可能破坏其他线程正在使用的数据结构或资源。
  3. 线程安全问题:编写线程安全的代码需要额外的努力,比如正确管理锁的使用、避免死锁和活锁等,这增加了开发的复杂度。如果线程间的交互没有正确处理,很容易引入难以发现和修复的错误。
  4. 调试挑战:多线程程序的调试比单线程程序更为复杂。由于线程执行的并发性和不确定性,错误可能不会稳定复现,且问题的原因可能隐藏在复杂的线程交互中,这使得调试和故障排查变得困难。

我们举个例子,我们故意触发异常:

#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_DEFERREDPTHREAD_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 的地址。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/12023.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【离散数学】偏序关系中盖住关系的求取及格论中有补格的判定(c语言实现)

实验要求 求n的因子函数 我们将n的因子存入数组中&#xff0c;n的因子就是可以整除n的数&#xff0c;所以我们通过一个for循环来求。返回因子个数。 //求n的因子,返回因子个数 int factors(int arr[], int n) {int j 0;for (int i 1; i < n; i){if (n % i 0){arr[j] i…

C++反向迭代器

C反向迭代器 反向迭代器是用正向迭代器适配实现的&#xff0c;本质是写一个反向迭代器的类模板&#xff0c;给编译器传不同的容器的正向迭代器实例化&#xff0c;编译器去实例化出各种类模板对应的反向迭代器。 #pragma once namespace my_reverse_iterator {template<cla…

关于使用git拉取gitlab仓库的步骤(解决公钥问题和pytho版本和repo版本不对应的问题)

先获取权限&#xff0c;提交ssh-key 虚拟机连接 GitLab并提交代码_gitlab提交mr-CSDN博客 配置完成上诉步骤之后&#xff0c;执行下列指令进行拉去仓库的内容 sudo apt install repo export PATHpwd/.repo/repo:$PATH python3 "实际路径"/repo init -u ssh://gitxx…

Java的类和对象(一)—— 初始类和对象,this关键字,构造方法

前言 从这篇文章开始&#xff0c;我们就进入到了JavaSE的核心部分。这篇文章是Java类和对象的第一篇&#xff0c;主要介绍类和对象的概念&#xff0c;this关键字以及构造方法~~ 什么是类&#xff1f;什么是对象&#xff1f; 学过C语言的老铁们&#xff0c;可以类比struct自定义…

spark结课之tip2

spark常用方法总结&#xff1a; 一、从内部创建RDD (1).通过并行化集合&#xff08;Parallelized Collections&#xff09;&#xff1a; 可以使用SparkContext的parallelize方法将一个已有的集合转换为RDD。 基本语法&#xff1a; parallelize(collection, numSlicesNone)…

AI系列:大语言模型的RAG(检索增强生成)技术(下)-- 使用LlamaIndex

目录 前言什么是LlamaIndex?LlamaIndex代码设置embedding模型设置LLM模型索引查询机 验证使用感受参考资料 前言 继上一篇文章AI系列&#xff1a;大语言模型的RAG&#xff08;检索增强生成&#xff09;技术&#xff08;上&#xff09;&#xff0c;这篇文章主要以LlamaIndex为…

银行业数据运营场景下的数据埋点方案

1、引言 随着金融科技的快速发展&#xff0c;银行业的数据运营变得日益重要。数据埋点作为数据收集的重要手段&#xff0c;对于银行业务的精细化运营、风险管理和产品迭代等方面起着至关重要的作用。本方案将针对银行业数据运营场景&#xff0c;设计一套完整的数据埋点方案&am…

【生信技能树】GEO数据挖掘全流程

R包的安装&#xff0c;每次做分析的时候先运行这段代码把R包都安装好了&#xff0c;这段代码不需要任何改动&#xff0c;每次分析直接运行。 options("repos""https://mirrors.ustc.edu.cn/CRAN/") if(!require("BiocManager")) install.packag…

思源笔记如何结合群晖WebDav实现云同步数据

文章目录 1. 开启群晖WebDav 服务2. 本地局域网IP同步测试3. 群晖安装Cpolar4. 配置远程同步地址5. 笔记远程同步测试6. 固定公网地址7. 配置固定远程同步地址 在数字化时代&#xff0c;信息的同步与共享变得尤为重要。无论是个人用户还是企业团队&#xff0c;都渴望能够实现跨…

创建存储过程

一、DDL与DML CREATE TABLE student (id INT PRIMARY KEY AUTO_INCREMENT,createDate DATETIME NOT NULL,userName VARCHAR(255) NOT NULL,phone VARCHAR(20) NOT NULL,age INT NOT NULL,sex ENUM(男, 女) NOT NULL,introduce TEXT ); INSERT INTO student (createDate, userN…

透明加密软件推荐:哪款实用又高效?

透明加密软件是一种专门针对文件保密需求的计算机加密工具。 其核心在于“透明”二字&#xff0c;意味着整个加密过程对于使用者来说是无形且无感知的。 当用户进行文件的日常操作&#xff0c;如打开、编辑或保存时&#xff0c;透明加密软件会在后台自动进行加密和解密工作&a…

Keil编程不同驱动文件引用同一个常量的处理方法

基础不牢&#xff0c;地动山摇&#xff0c;最近单片机编程又遇到一个基础问题。 我在头文件中定义了一个常量同时给两个驱动文件使用&#xff0c;封装的时候编译没问题&#xff0c;但是在main函数中引用驱动函数的时候就出现了重定义的问题&#xff0c;如下如所示。 解决方法很…

Windows 11 下 kafka 的安装踩坑

安装 windows系统kafka小白入门篇——下载安装&#xff0c;环境配置&#xff0c;入门代码书写&#xff08;推荐&#xff09; kafka在windows下安装和使用入门教程 问题1 参考链接 运行kafka集成的zookeeper时&#xff0c;命令&#xff1a;bin\windows\zookeeper-server-star…

05. 【Java教程】第一个 Java 程序

本节我们将以Windows操作系统为例&#xff0c;编写并执行第一个Java程序。在这之前&#xff0c;请确保你的操作系统上已经安装了JDK 1. 编译程序 大家可能有个疑问&#xff0c;为什么需要编译程序呢&#xff1f;计算机不能直接执行我们编写的源代码吗&#xff1f; 这是由于计…

CPU利用率使用教程

本文主要参考&#xff1a; 一文让你学到 nmon最详尽的用法 Linux性能监控命令_nmon 安装与使用 如果你是在Ubuntu上安装nmon&#xff0c;使用&#xff1a; apt install nmon安装好后&#xff0c;直接运行 $:nmon #运行如果是后台抓数据&#xff1a; -f 参数: 生成文件,文件…

python 虚拟环境多种创建方式

【一】说明介绍 &#xff08;1&#xff09;什么是虚拟环境 在Python中&#xff0c;虚拟环境&#xff08;Virtual Environment&#xff09;是一个独立的、隔离的Python运行环境&#xff0c;它拥有自己的Python解释器、第三方库和应用程序。通过创建虚拟环境&#xff0c;可以确…

【刷题(2)】矩阵

一、矩阵问题基础 遍历&#xff1a; for i in range(len(matrix[0])): for j in range(len(matrix): while 倒序遍历&#xff1a; for i in range(right,left,-1) 临时存储&#xff1a;temp w,h:len(matrix[0])-1 len(matrix)-1 left,right,top,bottom:0 len(matrix[0])-1 0 l…

Cesium 3DTileset Style 原理简析

Cesium 3DTileset Style 原理简析 应用层会看到这样的使用。那么原理是什么, 为啥写 height, 除了这个还有啥? const tileset await Cesium.Cesium3DTileset.fromUrl("../../public/tileset/building/tileset.json"); tileset.style new Cesium.Cesium3DTileSty…

HarmonyOS应用模型Stage基本介绍

文章目录 <font colorcoral> HarmonyOS应用模型概况<font colorcoral> Stage模型基本概念<font colorcoral> Stage模型UIAbiliry的生命周期<font colorcoral> Stage模型的配置文件<font colorcoral> 写在后面的话<font colorcoral>Referen…