Linux-线程知识点(2)

server/2024/11/15 6:16:37/

目录

  • 线程的取消
  • 线程的清理函数
  • 线程的局部存储
  • 向线程发送信号

线程的取消

Linux提供了如下函数来控制线程的取消:

int pthread_cancel(pthread_t thread);

一个线程可以通过该函数向另一个线程发送取消请求。这不是一个阻塞接口,发出请求后,函数就立刻返回了,而不是等待目标线程退出之后才返回。

如果成功,该函数返回0,失败返回错误码。

线程收到取消请求后,会采取什么行动呢?这取决于该线程的设定。NPTL提供了函数来设置线程是否允许取消,以及在允许取消的情况下,如何取消。

pthread_setcancelstate函数用来设置线程是否允许取消,函数定义如下:

int pthread_setcancelstate(int state, int *oldstate);

state参数有两种可能的值:

  • PTHREAD_CANCEL_ENABLE:线程的默认取消状态,如果是这种取消状态,会发生的事情取决于线程的取消类型;
  • PTHREAD_CANCEL_DISABLE:线程不理会取消请求,取消请求会被暂时挂起,不予理会。

pthread_setcanceltype函数用来设置线程的取消类型,其定义如下:

int pthread_setcanceltype(int type, int *oldtype);

取消类型有两种值:

  • PTHREAD_CANCEL_DEFERRED:延迟取消

  • PTHREAD_CANCEL_ASYNCHRONOUS:异步取消

PTHREAD_CANCEL_ASYNCHRONOUS为异步取消,即线程可能在任何时间点(可能是立即取消,但也不一定)取消线程。这种取消方式的最大问题在于,你不知道取消时线程执行到了哪一步。所以,这种取消方式太粗暴,很容易造成后续的混乱。因此不建议使用该取消方式。

PTHREAD_CANCEL_DEFERRED是延迟取消,线程会一直执行,直到遇到一个取消点,这种方式也是新建线程的默认取消类型。

取消点就是对于某些函数,如果线程允许取消且取消类型是延迟取消,并且线程也收到了取消请求,那么当执行到这些函数的时候,线程就可以退出了。

// 示例#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>void* thread_function(void* arg)
{// 设置线程的取消状态为可取消pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);// 设置线程的取消类型为延迟取消pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);printf("Thread is running...\n");while (1){// 在循环中检查取消请求// 把这段代码注掉也会取消,但是这种取消是线程随机的,在任意代码中取消,可能会造成内存泄露、资源无法释放等pthread_testcancel();// 线程执行的任务printf("Working...\n");sleep(1);}// 线程退出pthread_exit(NULL);
}int main() {pthread_t tid;// 创建线程if (pthread_create(&tid, NULL, thread_function, NULL) != 0){perror("pthread_create");exit(EXIT_FAILURE);}// 主线程等待一段时间后取消子线程sleep(3);printf("Canceling thread...\n");// 取消线程if (pthread_cancel(tid) != 0){perror("pthread_cancel");exit(EXIT_FAILURE);}// 等待线程结束if (pthread_join(tid, NULL) != 0){perror("pthread_join");exit(EXIT_FAILURE);}printf("Thread has been canceled\n");return 0;
}[root@Zhn 线程]# g++ pthread_cancel.cpp -o pthread_cancel -lpthread
[root@Zhn 线程]# ./pthread_cancel
Thread is running...
Working...
Working...
Working...
Canceling thread...
Thread has been canceled
[root@Zhn 线程]# 

对编程人员而言,应该遵循以下原则:

  • 第一,轻易不要调用pthread_cancel函数,在外部杀死线程是很糟糕的做法,毕竟如果想通知目标线程退出,还可以采取其他方法。
  • 第二,如果不得不允许线程取消,那么在某些非常关键不容有失的代码区域,暂时将线程设置成不可取消状态,退出关键区域之后,再恢复成可以取消的状态。
  • 第三,在非关键的区域,也要将线程设置成延迟取消,永远不要设置成异步取消。

线程的清理函数

假设遇到取消请求,线程执行到了取消点,却没有来得及做清理动作(如动态申请的内存没有释放,申请的互斥量没有解锁等),可能会导致错误的产生,比如死锁,甚至是进程崩溃。

// 示例void* cancel_unsafe(void*) 
{static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;pthread_mutex_lock(&mutex);              // 此处不是撤消点struct timespec ts = {3, 0};nanosleep(&ts, 0);                        // 是撤消点pthread_mutex_unlock(&mutex);          // 此处不是撤消点return 0;
}int main(void) 
{pthread_t t;pthread_create(&t, 0, cancel_unsafe, 0);pthread_cancel(t);pthread_join(t, 0);cancel_unsafe(0); // 发生死锁!return 0;
}

在上面的例子中,nanosleep是取消点,如果线程执行到此处时被其他线程取消,就会出现以下情况:互斥量还没有解锁,但持有锁的线程已不复存在。这种情况下其他线程再也无法申请到互斥量,很有可能在某处就会陷入死锁的境地。

为了避免这种情况,线程可以设置一个或多个清理函数,线程取消或退出时,会自动执行这些清理函数,以确保资源处于一致的状态。其相关接口定义如下:

void pthread_cleanup_push(void (*routine)(void *),void *arg);
void pthread_cleanup_pop(int execute);

第一个注意的点:

这两个函数必须同时出现,并且属于同一个语法块。

// 错误示范1
// 这两个函数在两个不同的函数内void foo()
{.....pthread_cleanup_pop(0).....
}
void *thread_work(void *arg)
{......pthread_cleanup_push(clean,clean_arg);......foo()......
}// 错误示范2
// 在日常编码中很容易犯上面这种错误。因为pthread_cleanup_push和phtread_cleanup_pop的实现中包含了{和},
// 所以将pop放入if{}的代码块中,会导致括号匹配错乱,最终会引发编译错误。
pthread_cleanup_push(clean_func,clean_arg);
......
if(cond)
{pthread_cleanup_pop(0);
}

第二个注意的点:

pthread_cleanup_push(clean_func_1,clean_arg_1)
pthread_cleanup_push(clean_func_2,clean_arg_2)
...
pthread_cleanup_pop(execute_2);
pthread_cleanup_pop(execute_1);

从push和pop的名字可以看出,这是栈的风格,后入先出,就是后注册的清理函数会先执行。其中pthread_cleanup_pop的用处是,删除注册的清理函数。如果参数是非0值,那么执行一次清理函数,再删除清理函数。否则的话,就直接删除清理函数。

第三个注意的点,何时会触发注册的清理函数:

  • 当线程的主函数是调用pthread_exit返回的,清理函数总是会被执行。
  • 当线程是被其他线程调用pthread_cancel取消的,清理函数总是会被执行。
  • 当线程的主函数是通过return返回的,并且pthread_cleanup_pop的唯一参数execute是0时,清理函数不会被执行。
  • 当线程的主函数是通过return返回的,并且pthread_cleanup_pop的唯一参数execute是非零值时,清理函数会执行一次。
// 示例#include <iostream>
#include<pthread.h>
#include<string.h>void clean(void* arg)
{std::cout << "Clean up: " << *reinterpret_cast<int*>(arg) << std::endl;
}void* thread(void* param)
{int input = *reinterpret_cast<int*>(param);printf("thread start\n");int* arg1 = new int(1);pthread_cleanup_push(clean, reinterpret_cast<void*>(arg1));int* arg2 = new int(2);pthread_cleanup_push(clean, reinterpret_cast<void*>(arg2));if (input != 0)/*pthread_exit退出,清理函数总会被执行*/pthread_exit(reinterpret_cast<void*>(arg1));pthread_cleanup_pop(0);pthread_cleanup_pop(0);/*return 返回,如果上面pop函数的参数是0,则不会执行清理函数*/int* revtal = new int(0);return (reinterpret_cast<void*>(revtal));
}int main()
{pthread_t tid;int* arg1 = new int;*arg1 = 0;int ret = pthread_create(&tid, NULL, thread, reinterpret_cast<void*>(arg1));if (ret != 0)return -1;void* res;pthread_join(tid, &res);std::cout << "first thread exit,return code is " << *reinterpret_cast<int*>(res) << std::endl;*arg1 = 1;ret = pthread_create(&tid, NULL, thread, reinterpret_cast<void*>(arg1));if (ret != 0)return -1;res = nullptr;pthread_join(tid, &res);std::cout << "second thread exit,return code is " << *reinterpret_cast<int*>(res) << std::endl;return 0;
}[root@Zhn 线程]# g++ pthread_cleanup.cpp -o pthread_cleanup -lpthread
[root@Zhn 线程]# ./pthread_cleanup 
thread start
first thread exit,return code is 0
thread start
Clean up: 2
Clean up: 1
second thread exit,return code is 1
[root@Zhn 线程]# 

线程的局部存储

// 示例
// 线程的局部存储#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <memory>pthread_key_t key;// 线程退出时调用
void destructor(void* value) {printf("Freeing memory for thread %ld\n", (long)pthread_self());free(value);
}void* thread_func(void* arg) {long thread_id = (long)arg;char* data = (char*)malloc(100);sprintf(data, "Data for thread %ld", thread_id);pthread_setspecific(key, data);// 获取线程局部存储中的数据char* retrieved_data = (char*)pthread_getspecific(key);printf("Thread %ld retrieved data: %s\n", thread_id, retrieved_data);return NULL;
}int main() {pthread_t threads[3];// 创建线程局部存储键pthread_key_create(&key, destructor);// 创建线程for (long i = 0; i < 3; ++i) {pthread_create(&threads[i], NULL, thread_func, (void*)i);}// 等待线程结束for (int i = 0; i < 3; ++i) {pthread_join(threads[i], NULL);}// 销毁线程局部存储键pthread_key_delete(key);return 0;
}[root@Zhn 线程]# g++ pthread_key_create.cpp -o pthread_key_create -lpthread
[root@Zhn 线程]# ./pthread_key_create 
Thread 0 retrieved data: Data for thread 0
Freeing memory for thread 139735337002752
Thread 1 retrieved data: Data for thread 1
Freeing memory for thread 139735328610048
Thread 2 retrieved data: Data for thread 2
Freeing memory for thread 139735320217344
[root@Zhn 线程]# 

线程的局部存储适用于许多不同的场景,特别是在需要处理多线程并发的程序中。以下是一些适合使用线程局部存储的场景:

  1. 线程安全的日志记录:每个线程可以有自己的日志记录器,而不必担心线程间的竞态条件。这样可以确保日志记录的线程安全性,而不需要额外的同步机制。
  2. 线程特定的配置参数:不同的线程可能需要不同的配置参数,例如线程池中的工作线程可能需要不同的超时设置或日志级别。使用线程局部存储可以轻松地为每个线程保存其特定的配置参数。
  3. 线程安全的资源管理:每个线程可能需要管理自己的资源,例如内存池、文件描述符等。通过线程局部存储,每个线程可以独立地管理自己的资源,避免了资源共享带来的竞态条件和同步问题。
  4. 线程特定的上下文信息:有些情况下,每个线程可能需要保存自己的上下文信息,例如线程池中的工作线程可能需要保存任务相关的上下文信息。线程局部存储可以用来保存这些线程特定的上下文信息,避免了上下文信息在线程间的共享和同步问题。
  5. 线程安全的数据缓存:每个线程可以有自己的数据缓存,例如线程池中的工作线程可以缓存一些计算结果或中间数据,以提高性能。使用线程局部存储可以确保数据缓存的线程安全性,而不需要额外的同步机制。

总之,线程的局部存储适用于需要在多线程环境下独立管理数据的场景,能够提高程序的可维护性、可扩展性和线程安全性。

向线程发送信号

pthread_kill函数接口如下:

int pthread_kill(pthread_t thread, int sig);
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <signal.h>
#include <unistd.h>// 信号处理函数
void signal_handler(int signum)
{if (signum == SIGUSR1) {printf("Received SIGUSR1 signal.\n");}else if (signum == SIGUSR2) {printf("Received SIGUSR2 signal.\n");}
}// 线程函数
void* thread_function(void* arg)
{// 注册信号处理函数signal(SIGUSR1, signal_handler);signal(SIGUSR2, signal_handler);printf("Thread is waiting for signals...\n");// 无限循环等待信号while (1) {// 睡眠1秒钟,以便演示sleep(1);}return NULL;
}int main() {pthread_t tid;// 创建线程if (pthread_create(&tid, NULL, thread_function, NULL) != 0) {fprintf(stderr, "Error creating thread.\n");return 1;}printf("Thread created successfully.\n");// 等待一段时间,以便线程有机会开始运行sleep(2);// 向线程发送信号pthread_kill(tid, SIGUSR1);sleep(2);pthread_kill(tid, SIGUSR2);// 等待线程结束pthread_join(tid, NULL);printf("Thread finished.\n");return 0;
}[root@Zhn 线程]# g++ pthread_signal.cpp -o pthread_signal -lpthread
[root@Zhn 线程]# ./pthread_signal 
Thread created successfully.
Thread is waiting for signals...
Received SIGUSR1 signal.
Received SIGUSR2 signal.

http://www.ppmy.cn/server/2161.html

相关文章

杰发科技AC7840——CAN通信简介(4)_过滤器设置

0. 简介 注意&#xff1a;过滤器最高三位用不到&#xff0c;因此最高位随意设置不影响过滤器。 1. 代码分析 注意设置过滤器数量 解释的有点看不懂 详细解释...也看不大懂 Mask的第0位是0&#xff0c;其他位都是1(就是F?)&#xff0c;那就指定了接收值就是这个数&#xff0c;…

【opencv】示例-stiching.cpp 图像拼接

#include "opencv2/imgcodecs.hpp" // 导入opencv图像编码功能库 #include "opencv2/highgui.hpp" // 导入opencv高层用户界面功能库 #include "opencv2/stitching.hpp" // 导入opencv图像拼接功能库#include <iostream> // 导入输入输出…

通过实例学C#之FileStream类

简介 可以通过此类进行文件读取。 首先在项目所在文件夹的Bin文件中新建一个test.txt文件&#xff0c;里面输入内容“hello world!”。 构造函数 FileStream (string path, FileMode mode&#xff0c;FileAccess access) 通过路径文件path&#xff0c;打开文件模式mode以及读写…

【LeetCode热题100】【贪心算法】跳跃游戏

题目链接&#xff1a;55. 跳跃游戏 - 力扣&#xff08;LeetCode&#xff09; 数组的元素表示可以跳的最大长度&#xff0c;要判断能不能跳到最后 不断更新可以跳到的最远距离&#xff0c;如果当前的位置大于可跳最远距离&#xff0c;说明不行 class Solution { public:bool …

计算机视觉入门:探索机器如何“看见”世界

计算机视觉是人工智能领域的一个令人兴奋的分支&#xff0c;它使计算机能够从图像和视频中“看见”和理解世界。这项技术已经渗透到我们生活的方方面面&#xff0c;从智能手机的面部识别到自动驾驶汽车的导航系统。但是&#xff0c;计算机视觉是如何工作的呢&#xff1f;让我们…

【Jupyter notebook】安装-换源-报错问题收集及实测解决法

Jupyter 在一个名为 kernel 的单独进程中运行用户的代码。kernel 可以是不同的 Python 安装在不同的 conda 环境或虚拟环境,甚至可以是不同语言(例如 Julia 或 R)的解释器。 1, 如何查询当前环境下的python 版本 运行 import sys print(sys.executable) %pwd import sy…

麒麟桌面操作系统使用livecd备份数据到U盘

往期好文链接&#xff1a;PXE批量部署麒麟服务器操作系统 Hello&#xff0c;大家好啊&#xff01;在使用计算机的过程中&#xff0c;数据备份是一个非常重要的环节&#xff0c;特别是当系统发生故障时&#xff0c;备份可以帮助我们迅速恢复重要数据。今天&#xff0c;我将向大家…

git rebase

如果你在Git中工作&#xff0c;并且你的当前开发分支落后于主分支&#xff0c;你可能希望将主分支上的最新更改合并到你的开发分支中以保持同步。同时&#xff0c;如果你不想在将来提交你的分支时包含主分支的合并提交信息&#xff0c;你可以考虑使用“rebase”而不是“merge”…