当前位置: 首页 > article >正文

Linux操作系统6- 线程3(线程的取消,分离与独立存储)

上篇文章:Linux操作系统6- 线程2(线程的创建,终止,等待与退出)-CSDN博客

本篇Gitee仓库:myLerningCode/l28 · 橘子真甜/Linux操作系统与网络编程学习 - 码云 - 开源中国 (gitee.com)

目录

一. 线程取消pthread_cancle

二. 线程分离pthread_detach⭐

2.1 主线程join时发生detach

2.2 正确的调用线程分离

三. Linux线程栈与独立存储 

3.1 线程的id


一. 线程取消pthread_cancle

        线程除了退出和终止,还能取消某一个线程。

//所需头文件
#include <pthread.h>

//用于取消thread这个线程
int pthread_cancle(pthread_t thread);

//注意,调用pthread_cancle之后该线程不会立即取消,而是在某些取消点取消
//(比如 sleep read write pthread_cond_wait)

//我们也能使用下面的调用来显示触发
void pthread_testcancel(void); // 显式触发取消检查

测试代码如下:

#include <iostream>
#include <string>
#include <vector>

#include <cstdlib>
#include <cassert>
#include <pthread.h>
#include <unistd.h>

using namespace std;
const int NUM = 10;

class ThreadData
{
public:
    int number;
    pthread_t tid;
    char namebuffer[64];
};

class ThreadReturn
{
public:
    int exit_code;
    int exit_result;
};

void *start_routine(void *arg) // 这个函数被多个线程进入,是可重入函数
{
    sleep(1);
    ThreadData *td = static_cast<ThreadData *>(arg);
    int cnt = 10;
    while (cnt)
    {
        cout << "new thread name:" << td->namebuffer << " cnt: " << cnt-- << " cnt地址:" << &cnt << endl;
        sleep(1);
    }

    ThreadReturn *tr = new ThreadReturn();
    tr->exit_code = 0;
    tr->exit_result = td->number;
    return (void *)1; // 右值
}

int main()
{
    // 1.创建一批线程
    vector<ThreadData *> threads; // 用于线程保存线程的数据
    for (int i = 0; i < NUM; i++)
    {
        ThreadData *td = new ThreadData();
        td->number = i;
        snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", i + 1);
        pthread_create(&td->tid, NULL, start_routine, td);
        threads.push_back(td);
    }
    for (auto &thread : threads)
    {
        cout << "creat thread: " << thread->namebuffer << ":" << thread->tid << " success" << endl;
    }
    sleep(5);

    // 线程取消,这里取消前5个线程
    for (int i = 0; i < threads.size() / 2; i++)
    {
        pthread_cancel(threads[i]->tid);
        cout << "cancel thread: " << threads[i]->namebuffer << " success" << endl;
    }

    // 创建时候循环创建,等待的时候一次性等待
    for (auto &thread : threads)
    {

        void *ret = nullptr;
        int n = pthread_join(thread->tid, &ret); // void **retp, *retp = ret , *ret = 值
        assert(n == 0);
        (void)n;

        cout << "join " << thread->namebuffer << " success exitcode:" << (long long)ret << endl;
        delete thread;
    }

    cout << "main thread quit" << endl;
    return 0;
}

        在创建线程然后主线程休眠5秒后取消5个线程,测试结果如下:

        可以看到,线程取消后,如果调用了join会等待线程退出 。不过我们不建议使用取消线程,直接使用线程退出即可。

        pthread_exit是线程自己运行完之后结束自己

        pthread_cancle一般用于外部结束某一个线程

二. 线程分离pthread_detach⭐

        使用pthread_join可以等待线程退出的信息。但是这种等待是阻塞式等待,如果主线程不想等待线程而继续执行其他代码呢?这时候就需要使用线程分离。

        我们创建进程之后,可以调用wait/waitpid来等待子进程退出,如果主进程想要不关心子进程的退出信息,而继续执行自己的代码。可以将子进程的退出信号设置为SIG_IGN。

        与进程退出一样,线程退出也有相似的操作来让主线程不用等待新线程。这种操作就是线程分离

//所需头文件
#include <pthread.h>

int pthread_detach(pthread_t thread)

//调用该函数后,会分离thread这个线程

2.1 主线程join时发生detach

测试代码如下:

        在下面这份代码中,新线程通过pthread_self自己分离自己,同时主线程调用pthread_join来等待新线程退出,看看会发生什么结果。

#include <iostream>
#include <string>
#include <vector>

#include <cstdlib>
#include <cassert>
#include <cstdio>
#include <cstring>

#include <pthread.h>
#include <unistd.h>

std::string changeId(const pthread_t &thread_id)
{
    char tid[128];
    snprintf(tid, sizeof(tid), "0x%x", thread_id);
    return tid;
}

void *start_routine(void *args)
{
    std::string name = static_cast<const char *>(args);
    sleep(1);   //让主线程先join,新线程再去分离
    pthread_detach(pthread_self()); // 线程将自己设置为分离

    int cnt = 5;
    while (cnt--)
    {
        std::cout << name << " is running! " << changeId(pthread_self()) << std::endl;
        sleep(1);
    }
    reutnr nullptr
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, NULL, start_routine, (void *)"thread 1");
    //sleep(1);
    std::string maintid = changeId(pthread_self());
    std::cout << "main tid:" << maintid << " newthread tid:" << changeId(tid) << std::endl;

    // 一个线程默认是joinable的,如果设置了分离,不会等待线程
    int n = pthread_join(tid, NULL);
    std::cout << "result " << n << ":" << strerror(n) << std::endl;
    return 0;
}

运行结果:

        可以看到,异常退出了。

 

         如果主线程正在join新线程的时候,这个线程调用了detach分离自己,那么就会发生这种错误。主线程join会立刻出错返回!

2.2 正确的调用线程分离

        为了线程的正常分离,我们一般在主线程中去分离想要分离的线程,且分离某一个线程之后就不要继续等待这个线程了,否则会出错返回。

#include <iostream>
#include <string>
#include <vector>

#include <cstdlib>
#include <cassert>
#include <cstdio>
#include <cstring>

#include <pthread.h>
#include <unistd.h>

std::string changeId(const pthread_t &thread_id)
{
    char tid[128];
    snprintf(tid, sizeof(tid), "0x%x", thread_id);
    return tid;
}

void *start_routine(void *args)
{
    std::string name = static_cast<const char *>(args);
    int cnt = 5;
    while (cnt--)
    {
        std::cout << name << " is running! " << changeId(pthread_self()) << std::endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, NULL, start_routine, (void *)"thread 1");
    pthread_detach(tid); // 主线程分离新线程

    std::string maintid = changeId(pthread_self());
    while (true)
    {
        std::cout << "main tid:" << maintid << " newthread tid:" << changeId(tid) << std::endl;
        sleep(1);
    }

    // 此时就不要等待新线程了!
    return 0;
}

运行结果如下:

三. Linux线程栈与独立存储 

         在Linux中,使用线程必须要包含pthread.h线程库。这意味着在Linux中:

轻量级进程:用户级线程 = 1 : 1。

用户关系的线程属性在库中,而线程的调度由cpu完成

3.1 线程的id

        我们使用pthread_create获取的id其实是:线程的其实地址,且这个地址位于虚拟空间的堆栈之间的共享区域。(准确的说是mmap区域)

证明如下:

#include <iostream>
#include <string>

#include <cstdlib>
#include <pthread.h>
#include <unistd.h>

void *start_routine(void *args)
{
    std::string name = static_cast<const char *>(args);
    sleep(10000);
    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, NULL, start_routine, (void *)"thread 1");
    pthread_detach(tid); // 主线程分离新线程

    int a = 10;
    int *b = new int;
    while (true)
    {
        printf("栈地址:%p\n", &a);
        printf("线程tid:%p\n", tid);
        printf("堆地址:%p\n", b);
        sleep(1);
    }

    // 此时就不要等待新线程了!
    return 0;
}

测试结果如下: 

虚拟地址空间的位置如下: 


http://www.kler.cn/a/588482.html

相关文章:

  • 反射(第三篇)、代理模式、静态代理和动态代理、InvocationHandler实际应用
  • 第十六届蓝桥杯康复训练--2
  • 新手村:数据预处理-缺失值补充策略
  • 【RabbitMQ】事务机制、限流、负载均衡
  • 玩转python:通俗易懂掌握高级数据结构-collections模块之Counter
  • 零基础掌握分布式ID生成:从理论到实战的完整指南 [特殊字符]
  • 563采药
  • NocoBase 本周更新汇总:双因素身份认证(2FA)
  • 蓝桥杯学习-08序列二分
  • 【动手学深度学习】#2线性神经网络
  • 火焰图分析Java程序瓶颈
  • 第15章:ConvNeXt图像分类实战:遥感场景分类【包含本地网页部署、迁移学习】
  • git subtree在本地合并子仓库到主仓库
  • KY-038 声音传感器如何工作以及如何将其与 ESP32 连接
  • java 线程池Executor框架
  • 深入解析 Vue 3 Teleport:原理、应用与最佳实践
  • 使用Inno Setup将Unity程序打成一个安装包
  • Native层逆向:ARM汇编与JNI调用分析
  • node.js-WebScoket心跳机制(服务器定时发送数据,检测连接状态,重连)
  • 游戏成瘾与学习动力激发策略研究——自我效能理论