Linux ---线程控制(二)

创建(删除)一个新线程的代价要比创建一个新进程小得多。

切换:与进程之间的切换(丢弃cache,重新进行命中缓存 )相比,线程之间的切换(不需要对cache进行切换)需要操作系统做的工作要少很多。

一、 创建线程

pthread_create 创建一个POSIX 线程(Linux 下常用轻量级线程),操作系统调度新线程执行指定入口函数,主线程与新线程并发运行。

参数说明

  1. pthread_t *thread 指针,函数成功后会把新线程的唯一 ID 写入该变量,后续 pthread_join/pthread_detach 靠此 ID 操作线程。
  2. pthread_attr_t *attr 线程属性结构体(栈大小、分离状态、调度优先级等),传 NULL 采用系统默认配置。
  3. void *(*start_routine)(void *) 线程执行函数,格式固定 :接收一个 void参数,返回 void
  4. void *arg 传递给线程函数的唯一参数,多参数可封装到结构体再传结构体指针。
cpp 复制代码
#include <iostream>
#include <cstdio>
#include <string>
#include <pthread.h>
#include <unistd.h>

void *Routine(void *args)
{
    std::string name = static_cast<const char *>(args);

    printf("--------------------------------------------------\n");

    while (true)
    {
        // std::cout<<"我是新线程:"<<name<<std::endl;
        printf("我是新线程:%s,tid:0x%lx,pid:%d\n", name.c_str(), pthread_self(), getpid());
        sleep(1);
    }
}

    int main()
    {
        int n;
        const int num = 10;
        for (int i = 0; i < num; i++)
        {
            pthread_t tid;
            char threadname[64];
            snprintf(threadname, sizeof(threadname), "thread-%d", i + 1);
            pthread_create(&tid, nullptr, Routine, threadname);
            (void)n;
            usleep(1);



        }

        // pthread_t tid;

        // int n=pthread_create(&tid,nullptr,Routine,(void*)"thread-1");
        // (void)n;
        // printf("new thread id :0x%lx\n",tid);
        // while(true)
        // {
        //     std::cout<<"我是主线程...."<<std::endl;
        //     sleep(1);

        //}

        while (true)
        {
            // std::cout<<"我是主线程...."<<std::endl;
            printf("我是主线程tid:0x%lx\n", pthread_self());
            sleep(1);
        }

        return 0;
    }

二、线程终止

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

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

三、线程等待

pthread_join

你的代码不加它会发生什么

你现在 main 创建线程后直接结束

  1. main 函数执行完毕 → 进程直接退出
  2. OS 会强制杀死所有子线程
  3. 子线程循环打印大概率只会输出一两行甚至完全不输出

加上pthread_join(tid, nullptr);后: 主线程停在这里死等子线程,子线程循环跑,不会被提前杀掉

调用 pthread_join 的线程会阻塞挂起,直到参数中指定 ID 的目标线程运行终止。 目标线程的终止方式不同,pthread_join 通过第二个参数 value_ptr 传出的终止状态也不一样,分四种情况:

  1. 目标线程通过 return 正常返回:value_ptr 指向的内存中,保存线程函数 return 的返回值。
  2. 目标线程被其他线程调用 pthread_cancel 取消终止:value_ptr 指向的内存中,存入常量 PTHREAD_CANCELED
  3. 目标线程自身调用 pthread_exit 主动退出:value_ptr 指向的内存中,存放传入 pthread_exit 的参数。
  4. 不需要获取线程终止状态:直接给 value_ptr 传入 NULL 即可。
cpp 复制代码
#include <iostream>
#include <cstdio>
#include <string>
#include <pthread.h>
#include <unistd.h>

class Res
{
public:
    int code;
        std::string name;
    std::string info;
};

void *Routine(void *args)
{
    std::string name = static_cast<const char *>(args);
    while (true)
    {
        std::cout << "new thread" << std::endl;
        sleep(1);
        break;
    }

    Res *res = new Res();
    res->code = 10;
    res->name = name;
    res->info = "我这个线程以及完蛋了,你们继续努力";
    return (void *)res;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, Routine, (void *)"thread-1");

    sleep(10);

    // 1、一般而言必须等待新线程退出,如果不等待,就会形成类似僵尸问题
    // 2、为了获取新线程执行的结果
    void *retval = nullptr;
    // 新线程出异常,进程就会全部退出,没机会join成功!不需要关心异常

    int n = pthread_join(tid, (void **)&retval); // 只关心正常情况

    if (n == 0)
    {
        Res  *p=(Res*)retval;
        std::cout << "join success" << p->code<< std::endl;
        std::cout << "join success" << p->name << std::endl;
        std::cout << "join success" << p->info << std::endl;
        delete p;
    }
    
    return 0;
}

四、分离线程

默认 情况下,新创建的线程是joinable的。线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄露

如果不关心线程的返回值,join是一种负担,这个时候我们可以告诉系统,当线程退出时,自动释放线程资源。(线程被分离)

线程创建后默认是 joinable(可汇合) 状态: 代表这个线程退出后,内核会保留它的资源、退出码,必须通过 pthread_join() 回收,否则会变成僵尸线程。

等待线程,是阻塞等待的,想让线程全部自动结束掉,主进程不想等待,把目标被等待的线程设置为:分离状态

一旦线程分离后,主线程就不管了。 分离是一种状态。

pthread_detach

返回值

cpp 复制代码
#include <iostream>
#include <cstdio>
#include <string>
#include <pthread.h>
#include <unistd.h>

class Res
{
public:
    int code;
        std::string name;
    std::string info;
};

void *Routine(void *args)
{
    int cnt=5;
    while(cnt)
    {
        std::cout<<"新线程在运行"<<cnt--<<std::endl;
        sleep(1);

    }

    return nullptr;

}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, Routine, (void *)"thread-1");
    std::cout<<"main thread 分离线程"<<std::endl;
    pthread_detach(tid);
    sleep(10);
}

执行结果是:

线程ID的理解及进程地址空间布局

线程局部存储

cpp 复制代码
#include <iostream>
#include <cstdio>
#include <string>
#include <pthread.h>
#include <unistd.h>


__thread int id=0;

void *routine(void *args)
{
    std::string name = static_cast<const char *>(args);
    while (true)
    {
        std::cout << "new thread id : " << id << std::endl;
        id++;
        sleep(1);
    }
    return nullptr;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, routine, (void *)"thread-1");
    while (true)
    {
        std::cout << "main thread id : " << id << std::endl;
        
        sleep(1);
    }

    pthread_join(tid, nullptr);

    return 0;
}

运行结果是:

从这里可以看到,在同一个全局变量,内核会给每一个创建出来的线程,各自分配一份独立的变量副本。子线程操作的是自己专属的id,主线程操作的是自己的专属id两份内存互相不占用、互相不修改。

要让线程局部存储:1、加__thread(两个_),只能修饰全局变量或者函数内static静态变量

2、变量每个线程各自一份副本,互相完全隔离

3、声明格式:__thread 数据类型 变量名=初始值;

相关推荐
蓝速科技3 分钟前
会议室门牌公告通知发布选型与落地指南丨蓝速科技
大数据·运维·数据库·人工智能·科技
Ivanqhz6 分钟前
SVD++算法
java·服务器·网络·深度学习·神经网络
chicheese33 分钟前
Linux 面试速查表:从初级到高级,附高频场景答题模板
linux·运维
Linux-lucky1 小时前
32-38-Linux学习之旅之MySQL综合
linux·运维·学习·mysql·ubuntu
智能运维指南2 小时前
2026年ITSM系统怎么选?四款主流方案与五维评估模型
运维·itsm·嘉为蓝鲸
vortex52 小时前
常用终端模拟器全面对比:Kitty、WezTerm、Ghostty 与 Konsole
linux
我命由我123452 小时前
Git 推送报错:error: src refspec main does not match any
运维·git·gitee·github·运维开发·学习方法·版本控制
骇客野人2 小时前
测试环境MySQL迁移Vastbase(海量数据库)完整改造与落地实施方案
运维·服务器
迪康Defender2 小时前
终端安全实战:如何高效解决企业U盘泄密与管控难题
运维·网络·安全·web安全·终端安全管理
Wang's Blog3 小时前
Java 项目实战: 外卖平台-删除分类的关联校验与自定义业务异常
java·服务器·项目开发