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

Linux_线程控制

线程控制的相关接口

进程创建相关

之前我们已经认识到了pthread_create函数用来创建线程,这里不再赘述。

pthread_self函数

void* routine(void* args)
{
    std::cout  << "我是新线程..." << pthread_self() << std::endl;
    return nullptr;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, routine, (void*)"thread");
    while(true)    sleep(1);
    return 0;
}

通过结果我们可以发现,得到了一个数字。实际上,使用 pthread_self 得到的这个数实际上是虚拟地址空间上的一个地址,通过这个地址,可以找到关于这个线程的基本信息,包括线程ID,线程栈,寄存器等属性;而真正的线程ID是通过 ps -aL | head -1 && ps -aL | grep mythread 命令得到的。

进程等待相关

为什么需要线程等待?

  • 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
  • 创建新的线程不会复用刚才退出线程的地址空间。
  • 若不等待,就会有内存泄露的问题。

pthread_join函数

参数说明

  • pthread_t thread:这是要等待的线程的标识符(ID),该标识符是由 pthread_create 函数返回的。
  • void **retval:这是一个指向 void * 指针的指针,用于接收被等待线程的返回值。

retval参数的可能取值(下面代码中都会体现):

  1. 如果thread线程通过return返回,retval所指向的单元里存放的是thread线程函数的返回值。
  2. 如果thread线程被别的线程调用pthread_ cancel异常终掉,retval所指向的单元里存放的是常数PTHREAD_ CANCELED(-1)。
  3. 如果thread线程是自己调用pthread_exit终止的,retval所指向的单元存放的是传给pthread_exit的参数。
  4. 如果对thread线程的终止状态不感兴趣,可以传NULL给retval参数。

如果成功,pthread_join 返回 0;如果失败,则返回错误码。

 ps. 如果需要退出的线程一直不退出,那么等待该线程的线程就会一直等待!

void* routine(void* args)
{
    std::cout  << "我是新线程..." << pthread_self() << std::endl;
    return nullptr;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, routine, (void*)"thread");
    
    // 等待
    int n = pthread_join(tid1, nullptr);
    if(n != 0)
        std::cerr << "join error: "<< n << ", " << strerror(n) << std::endl;
    std::cout << "join success!" << std::endl;

    while(true)    sleep(1);
    return 0;
}


我们将代码稍作修改

void* routine(void* args)
{
    while(true)
    {
        std::cout  << "我是新线程..." << toHex(pthread_self()) << std::endl;
        sleep(1);
        break;
    }
    // 返回值:可以是变量、数字、对象!
    return (void*)10;    // 将返回结果修改为10
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, routine, (void*)"thread");

    void *ret = nullptr;
    int n = pthread_join(tid, &ret);
    // 等待成功并打印ret的值
    std::cout << "join success!, ret: " << (long long int)ret << std::endl;
    return 0;
}

结论:ret的值其实就是routine的返回值!

ps. 理论上,堆空间也是共享的!谁拿着堆空间的入口地址,谁就能访问该堆区!栈空间也是如此!

Demo代码

// 创建多线程并等待的样例(传递参数为对象)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>

#define NUM 5

class ThreadDate
{
public:
    ThreadDate(){}
    void Init(int a, int b, std::string name)
    {   
        _a = a;
        _b = b;
        _name = name;
    }
    int Result()    {return _a + _b; }
    std::string Name(){return _name;}
    void SetId(pthread_t id){ _tid = id;}
    pthread_t Id(){return _tid;}
    int A(){return _a;}
    int B(){return _b;}
    ~ThreadDate(){}
private:
    int _a;
    int _b;
    int _result;
    std::string _name;
    pthread_t _tid;
};

void* routine(void* args)
{
    ThreadDate* td = static_cast<ThreadDate*>(args);

    std::cout << "我是新线程,我的名字是: " << td->Name() << std::endl;
    return nullptr;
}
int main()
{
    ThreadDate td[NUM];
    // 预处理
    for(int i = 0;i < NUM;i++)
    {
        char id[64];
        snprintf(id, sizeof(id), "thread-%d", i);
        td[i].Init(i * 10, i * 20, id);
    }
    // 创建多个线程
    for(int i = 0;i < NUM;i++)
    {
        pthread_t tid;
        pthread_create(&tid, nullptr, routine, &td[i]);
        td[i].SetId(tid);
    }
    // 等待多个线程
    for(int i = 0;i < NUM;i++)
    {
        pthread_join(td[i].Id(), nullptr);
    }
    // 汇总处理结果
    for(int i =0;i < NUM; i++)
    {
        printf("td[%d]: %d+%d=%d[%ld]\n", i, td[i].A(), td[i].B(), td[i].Result(), td[i].Id());
    }
    return 0;
}

进程终止模块

如果需要只终止某个线程而不终止整个进程,有三种方法:

  1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
  2. 线程可以调用pthread_ exit终止自己。
  3. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。

pthread_exit函数

 参数:

  • retval:这是一个指向任意数据的指针,该数据将被线程的终止状态所使用,并且可以被其他线程通过调用 pthread_join 来访问。
void* routine(void* args)
{
    std::string name = static_cast<const char*>(args);
    std::cout << "我是新线程..." << std::endl;
    pthread_exit((void*)10);    // 作用与return相同
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, routine, (void*)"thread");

    void* ret = nullptr;
    pthread_join(tid, &ret);
    std::cout << "new thread exit code: " << (long long int)ret << std::endl;
    return 0;
}

pthread_cancel函数

参数:

  • thread:要发送取消请求的线程标识符(pthread_t 类型)。
void* routine(void* args)
{
    std::string name = static_cast<const char*>(args);
    while(true){
        std::cout << "我是新线程..." << std::endl;
        sleep(1);
    }//该线程没有退出
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, routine, (void*)"thread");
    
    sleep(2);
    pthread_cancel(tid);
    std::cout << "取消线程: " << tid << std::endl;
    sleep(2);

    void* ret = nullptr;
    // 取消之后的线程的返回值为:-1:PTHREAD_CANCELED ((void *) -1)
    // 所以取消之后的线程也必须join,否则就会内存泄漏
    pthread_join(tid, &ret);    
    std::cout << "new thread exit code: " << (long long int)ret << std::endl;
    return 0;
}

线程分离模块

pthread_detach函数

void* routine(void* args)
{
    // 线程分离可以自己主动分离
    // pthread_detach(pthread_self());
    std::string name = static_cast<const char*>(args);
    while(true){
        std::cout << "我是新线程..." << std::endl;
        sleep(1);
    }
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, routine, (void*)"thread");
    // 线程分离也可以被主线程分离
    pthread_detach(tid);

    sleep(2);    //先让线程分离,在进行等待

    void* ret = nullptr;
    int n = pthread_join(tid, &ret);
    // pthread_join返回0代表等待成功,非0为失败
    std::cout << "new thread exit code: " << (long long int)ret << ", n: " << n <<  std::endl;
    return 0;
}

线程分离之后,pthread_join就不会阻塞等待被分离的线程,所以join就会失败。

线程局部存储

线程局部存储(Thread Local Storage,TLS)是一种特殊的存储机制,用于为每个线程提供独立的变量副本,确保线程之间的数据隔离。

__thread关键字实现线程局部存储

__thread int tls_variable = 0; // 每个线程都有独立的tls_variable副本

ps. __thread只能修饰内置类型 

优点:

  • 线程安全:每个线程访问自己的变量副本,避免了线程之间的数据竞争。

  • 性能优化:减少了锁的使用,提高了多线程程序的性能。

  • 灵活性:可以存储线程相关的上下文信息。

缺点:

  • 内存占用:每个线程都会有一个独立的变量副本,可能会增加内存占用。

  • 线程生命周期管理:需要确保线程结束时清理线程局部存储的资源,否则可能导致内存泄漏。


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

相关文章:

  • 电力场效应晶体管(电力 MOSFET),全控型器件
  • 国产编辑器EverEdit - 输出窗口
  • Linux将目录挂载到另一个目录,类似软硬链接,并通过fstab实现
  • TCP 三次握手四次挥手
  • 2025年美赛C题:奥运奖牌榜模型 解析及Python代码实现
  • 32、【OS】【Nuttx】OSTest分析(1):stdio测试(二)
  • TCP协议(网络)
  • Vue3在img标签中绑定数据模型中的url图片无法显示问题
  • 奇安信 2022 Zteam 面试(详细答案版)
  • 扣子平台音频功能:让声音也能“智能”起来
  • Solon Cloud Gateway 开发:Route 的匹配检测器及定制
  • 集群IB网络扫描
  • 使用 Docker 运行 Oracle Database 23ai Free 容器镜像并配置密码与数据持久化
  • 【架构面试】二、消息队列和MySQL和Redis
  • 批量提取多个 Excel 文件内指定单元格的数据
  • linux如何定位外部攻击并进行防御处理
  • Visual Studio Code修改terminal字体
  • 【pytorch】norm的使用
  • 9【如何面对他人学习和生活中的刁难】
  • 破解浏览器渲染“死锁”:CSS与JS如何影响页面加载速度?
  • GCC之编译(8)AR打包命令
  • 【初阶数据结构】逆流的回环链桥:双链表
  • 【单链表算法实战】解锁数据结构核心谜题——相交链表
  • 解决使用Selenium时ChromeDriver版本不匹配问题
  • [b01lers2020]Life on Mars1
  • 计算机视觉:撕裂时空的视觉算法革命狂潮