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 数据类型 变量名=初始值;

相关推荐
smartvxworks7 小时前
Linux 实时内核(Linux Real-Time Kernel)详解:原理、实践与优化
java·linux·服务器
梦痕长情7 小时前
等保三级三权账户
linux·运维·服务器
wuqingshun3141598 小时前
说说TCP的3次握手?为什么需要三次握手?
服务器·网络协议·tcp/ip
CHANG_THE_WORLD8 小时前
1.简单的服务端和客户端(仅作为内容补充)
linux·服务器·网络
jiecy8 小时前
为Linux系统双网卡配置静态路由
linux·运维
CodexDave9 小时前
数据库连接池耗尽:排查顺序与三层兜底
服务器·前端·数据库·git·云原生·容器·kubernetes
weixin_307779139 小时前
Linux下Jenkins数据故障的系统化排查Shell脚本
linux·运维·服务器·jenkins
荒--9 小时前
Burp Suite(二)
linux·运维·服务器