多线程编程杂谈( 下)
问题
是否存在其它中途线程退出的方法?
通过调用Linux系统函数 pthread_cancel(...) 可中途退出线程
Linux 提供了线程取消函数

取消状态
- 接受取消状态: PTHREAD_CANCEL_ENABLE
- 拒绝取消状态: PTHREAD_CANCEL_DISABLE
取消请求
- 延迟取消: PTHREAD_CANCEL_DEFERRED => 线程继续执行,在下一次取消掉退出执行
- 异步取消: PTHREAD_CANCEL_ASYNCHRONOUS => 可能在任何位置退出执行
什么是线程取消点?
取消点即特殊函数的调用点
- 线程允许取消并且取消类型是延迟取消
- 当接收到取消请求后,执行到特殊函数调用点时,线程退出
常用取消点函数
- void pthread_testcancel(void)
- 在需要退出的 "关键点" 调用此函数,线程返回值为 PTHREAD_CANCELED
线程被动退出示例

线程被动退出实验
test1.c
#define _GNU_SOURCE /* To get pthread_getattr_np() declaration */
#define _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <memory.h>
#include <semaphore.h>void cleanup_handler(void *arg)
{printf("%s: %p\n", __FUNCTION__, arg);free(arg);
}void* thread_func(void* arg)
{ int i = 0;char* pc = malloc(16); // initialize pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);if( pc ){pthread_cleanup_push(cleanup_handler, pc);printf("pc = %p\n", pc);strcpy(pc, "Hello World!");while( 1 ){ printf("pc = %s\n", pc);printf("begin...\n");pthread_testcancel();printf("end...\n");// sleep(1);} pthread_cleanup_pop(1); }return NULL;
}int main()
{pthread_t t = 0;void* ret = NULL;pthread_create(&t, NULL, thread_func, NULL);sleep(3);pthread_cancel(t);pthread_join(t, &ret);printf("ret = %lld\n", (long long)ret);printf("PTHREAD_CANCELED = %lld\n", (long long)PTHREAD_CANCELED);return 0;
}
第55行,在主线程中创建子线程 thread_func,来测试线程中途退出
第 22 行和 23行,在子线程中设置可以接收取消状态,取消类型为延迟取消
第 27 行和 44 行,通过 pthread_cleanup_push(...) 和 pthread_cleanup_pop(1),来设置线程清理函数为 cleanup_handler(...); 即使线程中途退出,线程清理函数也会被自动调用
第 38 行,通过 pthread_testcancel() 函数来设置线程的取消点,当代码执行到这个函数,并且收到了线程取消请求,线程就会中途退出了
第 59 行,在主线程 sleep 3s后,调用 pthread_cancel(...) 函数来通知子线程退出
第 63 行,打印子线程中途退出时的返回值
程序运行结果如下图所示:

子线程在 3s 后退出,通过 pthread_cancel(...) 来退出线程,该线程退出的返回值等同于 PTHREAD_CANCELED,值为 -1
实验总结
必须在线程中调用取消状态和取消类型的设置函数
除了 pthread_testcancel() 函数, sleep() 函数也是取消点
线程接收取消请求后,会执行线程清理函数 (释放资源)
进入临界区之前,将取消状态设置为 PTHREAD_CANCEL_DISABLE
退出临界区之后,可将取消状态重新设置为 PTHREAD_CANCEL_ENABLE
对于取消类型,永远不要使用 PTHREAD_CANCEL_ASYNCHRONOUS
线程与信号
信号是进程层面的概念,进程内的所有线程均可处理信号
进程收到信号后,任意挑选线程对信号进行处理 (未屏蔽目标信号)
可针对进程中特定的线程发送信号,此时只有目标线程收到信号
线程可独立设置各自的信号掩码 (独立配置目标信号集合)

线程信号发送示例

注意事项
"父线程" 中的信号屏蔽会传递到 "子线程" 中
发送给进程的信号首选主线程处理 (使用已注册的信号处理函数)
若主线程屏蔽目标信号,则选择其他未屏蔽目标信号的线程完成信号处理
线程中注册的信号处理函数,对于进程全局有效
A 线程注册处理函数 handler_x(), B 线程注册处理函数 handler_y()
A 线程和 B 线程 收到 x 和 y 信号均会调用对应的处理函数
重要提醒
在多线程程序中,使用信号的第一原则就是不要使用信号!
线程与信号实验
test2.c
#define _GNU_SOURCE /* To get pthread_getattr_np() declaration */
#define _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <memory.h>
#include <semaphore.h>static void mask_all_signal()
{sigset_t set = {0};sigfillset(&set);pthread_sigmask(SIG_SETMASK, &set, NULL);
}void signal_handler(int sig, siginfo_t* info, void* ucontext)
{printf("handler : thread = %ld\n", pthread_self());printf("handler : sig = %d\n", sig);printf("handler : info->si_signo = %d\n", info->si_signo);printf("handler : info->si_code = %d\n", info->si_code);printf("handler : info->si_pid = %d\n", info->si_pid);printf("handler : info->si_value = %d\n", info->si_value.sival_int);}static void* thread_entry(const char* name, int sig, void* arg)
{struct sigaction act = {0};act.sa_sigaction = signal_handler;act.sa_flags = SA_SIGINFO;sigaddset(&act.sa_mask, sig);sigaction(sig, &act, NULL);while( 1 ){printf("%s ==> %ld : run...\n", name, pthread_self());sleep(1);}return NULL;
}void* thread_func(void* arg)
{ return thread_entry(__FUNCTION__, 40, arg);
}void* thread_exit(void* arg)
{ return thread_entry(__FUNCTION__, SIGINT, arg);
}int main()
{pthread_t tf = 0;pthread_t te = 0;void* ret = NULL;union sigval sv = {1234567};printf("thread %ld : run...\n", pthread_self());pthread_create(&tf, NULL, thread_func, NULL);// mask_all_signal();pthread_create(&te, NULL, thread_exit, NULL);sleep(3);pthread_sigqueue(tf, 40, sv);sleep(3);pthread_kill(te, SIGINT);pthread_join(tf, &ret);pthread_join(te, &ret);return 0;
}
第 69 和 73 行,主线程创建了2个子线程
在子线程 thread_func 中,捕获信号值为40的信号,当收到值为40的信号后,调用信号捕捉函数 signal_handler()
在子线程 thread_exit 中,捕获SIGNAL信号,当收到SIGNAL的信号后,调用信号捕捉函数 signal_handler()
在主线程中先 sleep 3s,然后通过 pthread_sigqueue(...) 函数向 thread_func 发送值为 40 的信号,并携带了一个参数,这个参数的值为 1234567;随后又 sleep 3s,通过 pthread_kill(...) 向 thread_exit 发送 SIGNAL 信号
pthread_sigqueue(...) 和 pthread_kill(...)都是向线程发送信号,但 pthread_sigqueue(...)可以多携带一个参数
程序运行结果如下图所示:

两个子线程均收到了信号,并调用到了信号处理函数
我们在 shell 中键入 Ctrl C,向进程发送 SIGNAL 信号,结果如下图所示:

该信号被处理了,是主线程处理的,并且处理方式是在子线程 thread_exit 中设置的信号处理方式,说明发送给进程的信号首选主线程处理,线程中注册的信号处理函数,对于进程全局有效
将 71 行,mask_all_signal() 的注释打开,程序运行结果如下图所示:

通过打印可以看出,只有值为40的信号被捕获了,thread_exit 线程是在主线程调用 mask_all_signal(),屏蔽所有的信号后创建出来的,thread_exit 线程会继承主线程的信号屏蔽集,屏蔽所有信号
思考
主线程创建子线程,子线程执行过程中调用 fork(),会发生什么 ???
下面的程序输出什么?为什么?

多线程 fork() 实验
test3.c
#define _GNU_SOURCE /* To get pthread_getattr_np() declaration */
#define _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <memory.h>
#include <semaphore.h>pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;void* thread_func(void* arg)
{if( arg )fork();while(1){printf("thread_func : %d => %ld\n", getpid(), pthread_self());sleep(1);}
}int main()
{pthread_t t = 0;pthread_create(&t, NULL, thread_func, NULL);pthread_create(&t, NULL, thread_func, (void*)1);while(1){printf("main : %d => %ld\n", getpid(), pthread_self());sleep(1);}return 0;
}
程序运行结果如下图所示:

主线程创建了 2 个子线程,其中一个子线程调用了 fork(),可以看出在子线程中 fork(),创建出来的进程是在子线程的代码片段去执行的
问题出在哪里?
fork() 是针对进程复制的系统调用 (历史比较悠久)
线程在 Linux 内核中是轻量级进程 (fork() 只会复制当前进程)
所以,多线程中 fork() 之后:
整个进程的资源都被复制,如:全局变量,代码段,堆...
当前线程 (轻量级进程) 被复制,如:寄存器,执行流
其他线程不会被复制 (fork() 只针对当前进程)
应用场景 => 多进程服务端

多进程服务端的好处是即使一个进程崩溃了,也不会影响整个服务端的正常运行;并且在子线程中 fork() 去创建进程,上下文更简短,代码执行逻辑更加清晰
注意事项
多线程中的 fork() 可能导致死锁!

test4.c
#define _GNU_SOURCE /* To get pthread_getattr_np() declaration */
#define _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <memory.h>
#include <semaphore.h>pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;void* thread_2(void* arg)
{ int i = 0;sleep(1);if( fork() ){return NULL;}while( i < 100 ){pthread_mutex_lock(&g_mutex);printf("fork : %d => %ld\n", getpid(), pthread_self()); pthread_mutex_unlock(&g_mutex);sleep(1);i++;}return NULL;
}void* thread_1(void* arg)
{ int i = 0;while( i < 100 ){pthread_mutex_lock(&g_mutex);sleep(3);pthread_mutex_unlock(&g_mutex);i++;}return NULL;
}int main()
{pthread_t t = 0;pthread_create(&t, NULL, thread_1, NULL);pthread_create(&t, NULL, thread_2, NULL);printf("main : %d => %ld\n", getpid(), pthread_self());while(1){sleep(1);}return 0;
}
主线程中创建两个子线程,thread_1 和 thread_2,thread_1 会先获取到锁,然后 thread_2 执行 fork(),也去获取锁,由于 fork() 后整个进程资源都会被复制,fork() 前 g_mutx 已被上锁,所以 fork() 后去获取锁,会导致死锁
程序运行结果如下图所示:

fork() 后导致了死锁,所以在多线程中去执行 fork() 去获取锁的场景下,需要在 fork() 前先将需要获取的锁都进行解锁
相关文章:
多线程编程杂谈( 下)
问题 是否存在其它中途线程退出的方法? 通过调用Linux系统函数 pthread_cancel(...) 可中途退出线程 Linux 提供了线程取消函数 取消状态 接受取消状态: PTHREAD_CANCEL_ENABLE拒绝取消状态: PTHREAD_CANCEL_DISABLE 取消请求 延迟取消: PTHREAD_CANCEL_DEFERR…...
rdma-core debug
export MLX5_DEBUG_MASK0xff export MLX5_DEBUG_FILE/tmp/mlx5.txt git clone https://github.com/linux-rdma/rdma-core.git cd rdma-core ./build.sh 修改build/CMakeCache.txt MLX5_DEBUG:BOOLTRUE function install_rdma_core {local dir/swgwork/cmi/rdma-core/buil…...
电脑无法开机,重装系统后没有驱动且驱动安装失败
电脑无法开机,重装系统后没有驱动且驱动安装失败 前几天电脑突然坏了,电脑卡住后,强制关机,再开机后开机马上就关机。尝试无数次开机后失败,进入BIOS界面,发现已经没有Windows系统了。重新安装系统后&…...
【Java数据结构】了解排序相关算法
基数排序 基数排序是桶排序的扩展,本质是将整数按位切割成不同的数字,然后按每个位数分别比较最后比一位较下来的顺序就是所有数的大小顺序。 先对数组中每个数的个位比大小排序然后按照队列先进先出的顺序分别拿出数据再将拿出的数据分别对十位百位千位…...
机器学习-线性回归(对于f(x;w)=w^Tx+b理解)
一、𝑓(𝒙;𝒘) 𝒘T𝒙的推导 学习线性回归,我们那先要对于线性回归的表达公示,有所认识。 我们先假设空间是一组参数化的线性函数: 其中权重向量𝒘 ∈ R𝐷 …...
RAG与GraphRAG的区别
文章目录 前言RAG 的特点核心思想数据结构优势局限性应用场景 GraphRAG 的特点核心思想数据结构优势局限性应用场景 如何选型示例场景多跳推理问题推荐系统中的复杂关系社交网络中的影响力分析 总结 前言 RAG (Retrieval-Augmented Generation) 和 GraphRAG (Graph-Based Retr…...
Ubuntu环境通过Ollama部署DeepSeek-R1模型教程
Ollama 是一个专注于简化模型部署和推理的工具,特别适合在生产环境中快速部署和运行模型。 以下是如何使用 Ollama 来安装、部署和使用模型的步骤: 一. 安装 Ollama 首先,你需要安装 Ollama。Ollama 通常支持多种平台(如 Linux、…...
使用Ollama 在Ubuntu运行deepseek大模型:以deepseek-r1为例
deepseek大模型上热搜啦! 咱们来亲身感受下DeepSeek模型的魅力吧! 整个操作流程非常简单方便,只需要2步,先安装Ollama,然后执行大模型即可。 支持的deepseek-r1模型 deepseek-r1 DeepSeek-R1-Distill-Qwen-1.5B …...
【中间件快速入门】什么是Redis
现在后端开发会用到各种中间件,一不留神项目可能在哪天就要用到一个我们之前可能听过但是从来没接触过的中间件,这个时候对于开发人员来说,如果你不知道这个中间件的设计逻辑和使用方法,那在后面的开发和维护工作中可能就会比较吃…...
poi在word中打开本地文件
poi版本 5.2.0 方法1:使用XWPFFieldRun(推荐) 比如打开当前相对路径的aaaaa.docx XWPFFieldRun run paragraph.createFieldRun();CTRPr ctrPr run.getCTR().addNewRPr();CTFonts font ctrPr.addNewRFonts();// 设置字体font.setAscii(&quo…...
27. C语言 强制类型转换详解
本章目录: 前言强制类型转换(Type Casting)强制类型转换的语法示例1:将整数转换为浮点数输出结果: 代码解析: 整数提升(Integer Promotion)示例2:整数提升输出结果: 代码…...
【1】阿里面试题整理
[1]. Kafka如何保证数据一致性? Kafka主要通过副本机制、ISR机制、持久化机制以及事务机制等多种方式共同保证了数据的一致性。副本机制是Kafka确保数据一致性的基础,使用ISR(In-Sync Replica)机制来处理副本之间的同步,将消息持久化到硬盘中…...
MySQL知识点总结(十三)
执行逻辑备份要具备哪些条件,其优缺点在哪。 逻辑备份是温备,创建逻辑备份文件时,MySQL服务器必须处于运行状态,其他应用程序在逻辑备份期间不能修改但可以执行读取操作。逻辑备份会把表结构和数据转换为SQL语句保存。 逻辑备份…...
linux 环境安装 dlib 的 gpu 版本
默认使用 pip 安装的 dlib 是不使用 gpu 的 在国内社区用百度查如何安装 gpu 版本的 dlib 感觉信息都不太对,都是说要源码编译还有点复杂 还需要自己安装 cuda 相关的包啥的,看着就头大 于是想到这个因该 conda 自己就支持了吧,然后查了一下…...
Meta 计划 2025 年投资 650 亿美元推动 AI 发展
每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…...
第05章 12 可视化热量流线图一例
下面是一个使用VTK(Visualization Toolkit)和C编写的示例代码,展示如何在一个厨房模型中可视化热量流线图,并按照热量传递速度着色显示。这个示例假设你已经安装了VTK库,并且你的开发环境已经配置好来编译和运行VTK程序…...
微信小程序压缩图片
由于wx.compressImage(Object object) iOS 仅支持压缩 JPG 格式图片。所以我们需要做一下特殊的处理: 1.获取文件,判断文件是否大于设定的大小 2.如果大于则使用canvas进行绘制,并生成新的图片路径 3.上传图片 async chooseImage() {let …...
2025_1_27 C语言内存,递归,汉诺塔问题
1.c程序在内存中的布局 代码段(Code Segment) 位置:通常位于内存的最低地址。 用途:存储程序的可执行指令。 特点:只读,防止程序运行时被修改。数据段(Data Segment) 位置…...
K8s运维管理平台 - xkube体验:功能较多
目录 简介Lic安装1、需要手动安装MySQL,**建库**2、启动命令3、[ERROR] GetNodeMetric Fail:the server is currently unable to handle the request (get nodes.metrics.k8s.io qfusion-1) 使用总结优点优化 补充1:layui、layuimini和beego的详细介绍1.…...
舆情系统的情报搜索功能
引言 随着信息技术的发展和网络媒体的快速发展,舆情监测已成为各行各业不可或缺的工具。舆情系统中的情报搜索功能,作为其核心组成部分,能够帮助用户迅速、全面地捕捉互联网、社交平台、新闻媒体等渠道中的各类信息和舆论动态。情报搜索不仅提…...
简易CPU设计入门:控制总线的剩余信号(二)
项目代码下载 请大家首先准备好本项目所用的源代码。如果已经下载了,那就不用重复下载了。如果还没有下载,那么,请大家点击下方链接,来了解下载本项目的CPU源代码的方法。 CSDN文章:下载本项目代码 上述链接为本项目…...
[创业之路-270]:《向流程设计要效率》-2-企业流程架构模式 POS架构(规划、业务运营、支撑)、OES架构(业务运营、使能、支撑)
目录 一、POS架构 二、OES架构 三、POS架构与OES架构的差异 四、各自的典型示例 POS架构典型示例 OES架构典型示例 示例分析 五、各自的典型企业 POS架构典型企业 OES架构典型企业 分析 六、各自典型的流程 POS架构的典型流程 OES架构的典型流程 企业流程架构模式…...
9【如何面对他人学习和生活中的刁难】
我们在学习的过程中,会遇到很多来自于他人的刁难与嘲讽,如果处理不好,这会大大影响我们的心情,从而影响学习的效率 我建议,如果你学习或生活中也遇到了类似的问题,不要去生气,更不要发生冲突&a…...
脚本/编译安装nginx1.11.10
1、通过脚本安装nginx1.11.10 在保证yum源正常(国内源)的情况下,这个脚本是可以正常安装的–with-pcre/usr/src/pcre-8.12/ # 如果自带的pcre无效就使用这个自定义pcre的路径(pcre安装在第3步骤) #!/bin/bash#安装nginx所需依赖包 yum -y install pcre* pcre-dev…...
基于迁移学习的ResNet50模型实现石榴病害数据集多分类图片预测
完整源码项目包获取→点击文章末尾名片! 番石榴病害数据集 背景描述 番石榴 (Psidium guajava) 是南亚的主要作物,尤其是在孟加拉国。它富含维生素 C 和纤维,支持区域经济和营养。不幸的是,番石榴生产受到降…...
基于PostgreSQL的自然语义解析电子病历编程实践与探索(上)
一、引言 1.1研究目标与内容 本研究旨在构建一个基于 PostgreSQL 的自然语义解析电子病历编程体系,实现从电子病历文本中提取结构化信息,并将其存储于 PostgreSQL 数据库中,以支持高效的查询和分析。具体研究内容包括: 电子病历的预处理与自然语言处理:对电子病历文本进…...
5.1.3 软件过程评估
文章目录 软件能力成熟度模型CMM能力成熟度模型集成 软件能力成熟度模型CMM 软件能力成熟度模型是用于评价软件承接方能力的方法,通过评价,也可以让承接方看到自身缺陷,不断改进和提升软件过程能力。分为5个成熟度等级,初始级、可…...
【JavaEE】Spring(5):Mybatis(上)
一、什么是Mybatis Mybatis是一个持久层的框架,它用来更简单的完成程序和数据库之间的交互,也就是更简单的操作和读取数据库中的数据 在讲解Mybatis之前,先要进行一些准备工作: 1. 为项目添加 Mybatis 相关依赖 2. 创建用户表以…...
记录 | MaxKB创建本地AI智能问答系统
目录 前言一、重建MaxKBStep1 复制路径Step2 删除MaxKBStep3 创建数据存储文件夹Step4 重建 二、创建知识库Step1 新建知识库Step2 下载测试所用的txtStep3 上传本地文档Step4 选择模型补充智谱的API Key如何获取 Step5 查看是否成功 三、创建应用Step1 新建应用Step2 配置AI助…...
【Spring】Spring启示录
目录 前言 一、示例程序 二、OCP开闭原则 三、依赖倒置原则DIP 四、控制反转IOC 总结 前言 在软件开发的世界里,随着项目的增长和需求的变化,如何保持代码的灵活性、可维护性和扩展性成为了每个开发者必须面对的问题。传统的面向过程或基于类的设计…...
