多线程编程杂谈( 下)

news2025/1/31 6:19:53

问题

是否存在其它中途线程退出的方法?

通过调用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() 前先将需要获取的锁都进行解锁

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

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

相关文章

电脑无法开机,重装系统后没有驱动且驱动安装失败

电脑无法开机&#xff0c;重装系统后没有驱动且驱动安装失败 前几天电脑突然坏了&#xff0c;电脑卡住后&#xff0c;强制关机&#xff0c;再开机后开机马上就关机。尝试无数次开机后失败&#xff0c;进入BIOS界面&#xff0c;发现已经没有Windows系统了。重新安装系统后&…

【Java数据结构】了解排序相关算法

基数排序 基数排序是桶排序的扩展&#xff0c;本质是将整数按位切割成不同的数字&#xff0c;然后按每个位数分别比较最后比一位较下来的顺序就是所有数的大小顺序。 先对数组中每个数的个位比大小排序然后按照队列先进先出的顺序分别拿出数据再将拿出的数据分别对十位百位千位…

机器学习-线性回归(对于f(x;w)=w^Tx+b理解)

一、&#x1d453;(&#x1d499;;&#x1d498;) &#x1d498;T&#x1d499;的推导 学习线性回归&#xff0c;我们那先要对于线性回归的表达公示&#xff0c;有所认识。 我们先假设空间是一组参数化的线性函数&#xff1a; 其中权重向量&#x1d498; ∈ R&#x1d437; …

Ubuntu环境通过Ollama部署DeepSeek-R1模型教程

Ollama 是一个专注于简化模型部署和推理的工具&#xff0c;特别适合在生产环境中快速部署和运行模型。 以下是如何使用 Ollama 来安装、部署和使用模型的步骤&#xff1a; 一. 安装 Ollama 首先&#xff0c;你需要安装 Ollama。Ollama 通常支持多种平台&#xff08;如 Linux、…

【中间件快速入门】什么是Redis

现在后端开发会用到各种中间件&#xff0c;一不留神项目可能在哪天就要用到一个我们之前可能听过但是从来没接触过的中间件&#xff0c;这个时候对于开发人员来说&#xff0c;如果你不知道这个中间件的设计逻辑和使用方法&#xff0c;那在后面的开发和维护工作中可能就会比较吃…

poi在word中打开本地文件

poi版本 5.2.0 方法1&#xff1a;使用XWPFFieldRun&#xff08;推荐&#xff09; 比如打开当前相对路径的aaaaa.docx XWPFFieldRun run paragraph.createFieldRun();CTRPr ctrPr run.getCTR().addNewRPr();CTFonts font ctrPr.addNewRFonts();// 设置字体font.setAscii(&quo…

Meta 计划 2025 年投资 650 亿美元推动 AI 发展

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

2025_1_27 C语言内存,递归,汉诺塔问题

1.c程序在内存中的布局 代码段&#xff08;Code Segment&#xff09; 位置&#xff1a;通常位于内存的最低地址。 用途&#xff1a;存储程序的可执行指令。 特点&#xff1a;只读&#xff0c;防止程序运行时被修改。数据段&#xff08;Data Segment&#xff09; 位置&#xf…

K8s运维管理平台 - xkube体验:功能较多

目录 简介Lic安装1、需要手动安装MySQL&#xff0c;**建库**2、启动命令3、[ERROR] GetNodeMetric Fail:the server is currently unable to handle the request (get nodes.metrics.k8s.io qfusion-1) 使用总结优点优化 补充1&#xff1a;layui、layuimini和beego的详细介绍1.…

舆情系统的情报搜索功能

引言 随着信息技术的发展和网络媒体的快速发展&#xff0c;舆情监测已成为各行各业不可或缺的工具。舆情系统中的情报搜索功能&#xff0c;作为其核心组成部分&#xff0c;能够帮助用户迅速、全面地捕捉互联网、社交平台、新闻媒体等渠道中的各类信息和舆论动态。情报搜索不仅提…

简易CPU设计入门:控制总线的剩余信号(二)

项目代码下载 请大家首先准备好本项目所用的源代码。如果已经下载了&#xff0c;那就不用重复下载了。如果还没有下载&#xff0c;那么&#xff0c;请大家点击下方链接&#xff0c;来了解下载本项目的CPU源代码的方法。 CSDN文章&#xff1a;下载本项目代码 上述链接为本项目…

[创业之路-270]:《向流程设计要效率》-2-企业流程架构模式 POS架构(规划、业务运营、支撑)、OES架构(业务运营、使能、支撑)

目录 一、POS架构 二、OES架构 三、POS架构与OES架构的差异 四、各自的典型示例 POS架构典型示例 OES架构典型示例 示例分析 五、各自的典型企业 POS架构典型企业 OES架构典型企业 分析 六、各自典型的流程 POS架构的典型流程 OES架构的典型流程 企业流程架构模式…

基于迁移学习的ResNet50模型实现石榴病害数据集多分类图片预测

完整源码项目包获取→点击文章末尾名片&#xff01; 番石榴病害数据集 背景描述 番石榴 &#xff08;Psidium guajava&#xff09; 是南亚的主要作物&#xff0c;尤其是在孟加拉国。它富含维生素 C 和纤维&#xff0c;支持区域经济和营养。不幸的是&#xff0c;番石榴生产受到降…

基于PostgreSQL的自然语义解析电子病历编程实践与探索(上)

一、引言 1.1研究目标与内容 本研究旨在构建一个基于 PostgreSQL 的自然语义解析电子病历编程体系,实现从电子病历文本中提取结构化信息,并将其存储于 PostgreSQL 数据库中,以支持高效的查询和分析。具体研究内容包括: 电子病历的预处理与自然语言处理:对电子病历文本进…

5.1.3 软件过程评估

文章目录 软件能力成熟度模型CMM能力成熟度模型集成 软件能力成熟度模型CMM 软件能力成熟度模型是用于评价软件承接方能力的方法&#xff0c;通过评价&#xff0c;也可以让承接方看到自身缺陷&#xff0c;不断改进和提升软件过程能力。分为5个成熟度等级&#xff0c;初始级、可…

【JavaEE】Spring(5):Mybatis(上)

一、什么是Mybatis Mybatis是一个持久层的框架&#xff0c;它用来更简单的完成程序和数据库之间的交互&#xff0c;也就是更简单的操作和读取数据库中的数据 在讲解Mybatis之前&#xff0c;先要进行一些准备工作&#xff1a; 1. 为项目添加 Mybatis 相关依赖 2. 创建用户表以…

记录 | MaxKB创建本地AI智能问答系统

目录 前言一、重建MaxKBStep1 复制路径Step2 删除MaxKBStep3 创建数据存储文件夹Step4 重建 二、创建知识库Step1 新建知识库Step2 下载测试所用的txtStep3 上传本地文档Step4 选择模型补充智谱的API Key如何获取 Step5 查看是否成功 三、创建应用Step1 新建应用Step2 配置AI助…

【Spring】Spring启示录

目录 前言 一、示例程序 二、OCP开闭原则 三、依赖倒置原则DIP 四、控制反转IOC 总结 前言 在软件开发的世界里&#xff0c;随着项目的增长和需求的变化&#xff0c;如何保持代码的灵活性、可维护性和扩展性成为了每个开发者必须面对的问题。传统的面向过程或基于类的设计…

八股——Java基础(四)

目录 一、泛型 1. Java中的泛型是什么 ? 2. 使用泛型的好处是什么? 3. Java泛型的原理是什么 ? 什么是类型擦除 ? 4.什么是泛型中的限定通配符和非限定通配符 ? 5. List和List 之间有什么区别 ? 6. 可以把List传递给一个接受List参数的方法吗&#xff1f; 7. Arra…

基于STM32的循迹小车设计与实现

1 系统方案设计 根据系统设计功能&#xff0c;展开基于STM32的循迹小车设计&#xff0c;整体设计框图如图2.1所示。系统采用STM32单片机作为控制器,通过L298驱动器控制两个直流电机实现对小车的运动控制&#xff0c;两路红外模块实现黑线的检测&#xff0c;HC-SR04超声波模块实…