[Linux#40][线程] 线程控制 | 多线程

内核中有没有很明确的线程概念呢?没有的。有的是轻量级进程的概念

不会给我直接提供线程的系统调用,只会给我们提供轻量级进程的系统调用,但是我们用户,需要线程的接口!

所以 Linux 开发者提供了 pthread 线程库--应用层--轻量级接口进行封装。为用户提供直接线程的接口

  • 几乎所有的 Linux 平台,都是默认自带这个库的!
  • Linux 中编写多线程代码 需要 使用第三方 pthread

为了保证函数能接受任意指针类型,C 进行了泛型设计void *,之后进行强转即可

windows 指针 4 字节 (默认 32 位),Linux 指针 8 字节(uname -r 发现x86_64)

1. 线程创建

功能:创建一个新的线程

函数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (*start_routine)(void*), void *arg);

参数:

  • thread:输出型参数,获取创建成功的线程ID(是个地址)
  • attr:设置线程的属性,attr为nullptr表示使用默认属性
  • start_routine:是个函数地址,线程启动后要执行的函数
  • arg:传给线程启动函数的参数,不需要使可以设置为++nullptr++ (要传指针,才能切实拿到)

返回值:成功返回0,失败返回错误码

测试 :可以发现有两个执行流

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

using namespace std;

void* start_rountine(void* args)
{
    //安全的进行强制类型转化
    string name=static_cast<const char*>(args);
    while(true)
        {
            cout<<"new thread create success, name: "<<name<<endl;
            sleep(1);
        }
}

int main()
{

    #define NUM 10
    for(int i=0;i<NUM;++i)
        {
            pthread_t id;
            //pthread_create(&id,nullptr,start_rountine,(void*)"thread new"); 
            char namebuffer[64];
            snprintf(namebuffer,sizeof(namebuffer),"%s:%d","thread",i);
            pthread_create(&id,nullptr,start_rountine,namebuffer); 
        }


    while(true)
        {
            cout<<"new thread create success, name: main thread"<<endl;
            sleep(1);
        }

    return 0;
}

多线程发生了一些错乱,但还是可以发现是同时运行的

⭕注意:格式化传参的设置

复制代码
char namebuffer[64];
snprintf(namebuffer,sizeof(namebuffer),"%s:%d","thread",i);
pthread_create(&id,nullptr,start_rountine,namebuffer); 

第一行: char namebuffer[64];

这一行声明了一个字符数组 namebuffer,它可以存储最多 64 个字符(包括字符串结束符 \0)。

第二行: snprintf(namebuffer,sizeof(namebuffer),"%s:%d","thread",i);

这里使用了 snprintf 函数来格式化字符串并安全地写入到 namebuffer 中。snprintf 函数通常用于格式化输出,但会检查目标缓冲区的大小以防止溢出。参数解释如下:

  • namebuffer: 目标缓冲区。
  • sizeof(namebuffer): 缓冲区的大小(字节数),这里为 64。
  • "%s:%d": 格式字符串,表示一个字符串后跟一个冒号和一个整数。
  • "thread": 要插入的第一个参数,是一个字符串。
  • i: 要插入的第二个参数,是一个整数变量。

因此,这条语句将把 "thread:i" 的格式化字符串写入 namebuffer,其中 i 是某个整数值。

第三行: pthread_create(&id,nullptr,start_routine,namebuffer);

  • namebuffer: 这是传递给线程启动例程 start_routine 的参数,即包含 "thread:i" 的字符串。

线程的第三方库,不是系统调用,g++后需要-lpthread链接库

ps -aL 查看轻量级进程, LWP--light weight process

PID==LWP ,表面这个线程是主线程

  • 任何一个线程被干掉了,进程都会被干掉
  • 一个函数可以被多个执行流同时执行。叫做函数被重入了
  • 全局变量是所有线程共享的,都可以访问操作
  • 调度器决定运行顺序,我们并不只知道,但肯定是主线程最后退出

循环监视窗口的打开:while :; do ps -aL | grep mythread;sleep 1;done

2. 线程等待

  1. 类似于存在僵尸,需要等待退出
  2. 获取子进程退出结果
  • 参数:
    • thread:线程ID
    • retval:利用其带回线程返回值,需深刻理解
  • 返回值:
    • 线程等待成功返回0,失败返回错误码

      void threadRoutine(void args)
      {
      return (void *)233; // 返回给主线程
      }

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

      复制代码
      void *ret = nullptr;
      pthread_join(tid, &ret); // 默认会阻塞等待新线程退出
      
      cout << "new thread retval:" << (long long)(ret) << endl;
      return 0;

      }

  • main thread 等待的时候,默认是阻塞等待的!
  • 主线程需要 join --对创建线程进行回收

⭕void** retval :指向指针的指针,为了调用了接口,获取线程退出的退出码

对指针解引用,代表指针所指向的目标

将拿到的返回值空间存储到自己的地址空间中,所以就是一个二级指针了,保证了实参的传递,取地址解引用得到函数返回值,存取到用户指针中。例如:++解两次引用,就可以获取函数原值了++

  • 直接调用 exit,会全部都直接退出
  • exit 是用来终止进程的!不能用来直接终止进程!

3. 线程终止

仅代表线程终止

  • return
  • pthread_exit

放在调用函数结尾:

复制代码
void* start_rountine(void* args)
{
    //安全的进行强制类型转化
    ThreadDate* td=static_cast<ThreadDate*>(args);
    int cnt=10;
    while(cnt)
    {
        cout<<"cnt: "<<cnt<<" &cnt"<<&cnt<<endl;
        cnt--;
        sleep(1);
        pthread_exit(nullptr);
    }
    return nullptr;
}

线程取消

  • pthread_cancel -1

库设置的返回值,可以查看到 一个线程如果是被取消的,退出码是-1。

复制代码
void* start_rountine(void* args)
{
    //安全的进行强制类型转化
    ThreadDate* td=static_cast<ThreadDate*>(args);
    int cnt=5;
    while(cnt)
    {
        cout<<"cnt: "<<cnt<<" &cnt"<<&cnt<<endl;
        cnt--;
        sleep(1);
    }

    //正常跑完返回的100,那被取消的线程返回的是什么呢?
    return (void*)100;
}


int main()  
{
    vector<ThreadDate*> threads;
#define NUM 10
    for(int i=0;i<NUM;++i)
    {

        ThreadDate* td=new ThreadDate();
        td->number=i+1;
        snprintf(td->namebuffer,sizeof(td->namebuffer),"%s:%d","thread",i+1);
        pthread_create(&td->tid,nullptr,start_rountine,td); 
        //这样不仅每个线程数据除了自己拿到了,主线程也全部拿到了
        threads.push_back(td);
    }


    for(auto& iter:threads)
    {
        cout<<"create thread: "<<iter->namebuffer<<" : "<<iter->tid<<" success"<<endl;
    }

    //线程取消
    sleep(5);//先让线程跑起来
    for(int i=0;i<threads.size()/2;++i)
    {
        pthread_cancel(threads[i]->tid);//创建多线程组中的某一 进行取消
        cout<<"pthread_cancel: "<<threads[i]->namebuffer<<" success"<<endl;
    }

    for(auto& iter:threads)
    {
        void* ret=nullptr;//注意是void*
        int n=pthread_join(iter->tid,&ret);//&地址是void**  
        assert(n == 0);
        (void)n;
        cout<<"join : "<<iter->namebuffer<<" success , number: "<<(long long)ret<<endl;
        delete iter;
    }
    //这里就可以看出主线程式阻塞式的等待,全部等待成功,才打印这句话
    cout<<"main thread quit!!"<<endl;

    return 0;
} 

pthread_cancel(threads[i]->tid);//创建多线程组中的某一 进行取消

4.线程实现通信 | C++中

线程的参数和返回值,不仅仅可以用来进行传递一般参数,也可以传递类的对象!!

举例:实现通信

结构体+初始化=>类

复制代码
class Request
{
public:
    Request(int start, int end, const string &threadname)
    : start_(start), end_(end), threadname_(threadname)
    {}
public:
    int start_;
    int end_;
    string threadname_;
};

class Response
{
public:
    Response(int result, int exitcode):result_(result),exitcode_(exitcode)
    {}
public:
    int result_;   // 计算结果
    int exitcode_; // 计算结果是否可靠
};

进行测试:

将 rq 的内容传给线程函数,对 rq 获取内容执行 rsp 运算,释放 rq,返回 rsp 打印

复制代码
void *sumCount(void *args) // 线程的参数和返回值,不仅仅可以用来进行传递一般参数,也可以传递对象!!
{
    Request *rq = static_cast<Request*>(args); //  Request *rq = (Request*)args
    //强制转化
    Response *rsp = new Response(0,0);
    for(int i = rq->start_; i <= rq->end_; i++)
    {
        cout << rq->threadname_ << " is runing, caling..., " << i << endl;
        rsp->result_ += i;
        usleep(100000);
    }
    delete rq;//释放空间
    return rsp;
}

int main()
{
    pthread_t tid;
    Request *rq = new Request(1, 100, "thread 1");//参数指针
    pthread_create(&tid, nullptr, sumCount, rq);


    void *ret;
    pthread_join(tid, &ret);//接收指针的地址,实现传递
    Response *rsp = static_cast<Response *>(ret);
    cout << "rsp->result: " << rsp->result_ << ", exitcode: " << rsp->exitcode_ << endl;
    delete rsp;
    return 0;
}

要用地址符号接受,接收到的也是回复类的地址

pthread_join(tid, &ret);//接收指针的地址,实现传递

Response *rsp = static_cast<Response *>(ret);

也可以反映出堆空间也是被线程共享的

  • 目前,我们的原生线程,pthread库,原生线程库
  • C++11 语言本身也已经支持多线程了 vs 原生线程库

C++ 的多线程就是封装原生线程库

复制代码
void threadrun()
{
    while(true)
    {
        cout << "I am a new thead for C++" << endl;
        sleep(1);
    }
}

int main()
{
    thread t1(threadrun);//调用

    t1.join();//等待

    return 0;
}

thread t1(threadrun); //调用线程函数

编译时注意!两个编译后缀都要带 g++ -o $@$^-std=c++11 -lpthread

windows 下装的是 windows c++的库,++安装的是不同的库,所以语言具有跨平台性~++


5. 打印线程自己的 id

复制代码
pthread_create(&tid, nullptr, threadRoutine, (void*)"thread 1");
cout<<"thread id:"<<pthread_self()<<endl;


//对16进制转化的实现
std::string toHex(pthread_t tid)
{
    char hex[64];
    snprintf(hex, sizeof(hex), "%p", tid);//重点研究理解
    return hex;
}

man clone专门用来创建轻量级进程,我们使用的库底层就是它

线程的概念,是库给我们来维护的,你用的原生线程库,要不要加载到内存里,加载到哪里?

  • 要--都是基于内存的
  • 线程库要维护线程概念--不用维护线程的执行流,线程库注定了要维护多个线程属性集合。线程库要不要管理这些线程呢?要,先描述再组织
  • 由用户维护,OS 之上的,所以称为用户级线程

库加载到了共享区,在堆栈之间

  • 每一个线程的库级别的 tcb 的起始地址,叫做线程的 tid
  • 除了主线程,所有其他线程的独立栈,都在共享区,具体来讲是在 pthread 库中,tid 指向的用户 tcb 中

下篇文章将讲解线程的互斥~

相关推荐
肖恩想要年薪百万3 分钟前
JSP中常用JSTL标签
java·开发语言·状态模式
2401_840192275 分钟前
k8s的crd、operator、cr分别是什么?
运维·分布式·kubernetes·prometheus
草木深雨纷纷6 分钟前
mt管理器手机版下载2026最新版更新下载分享
linux·运维·网络·智能手机
扛枪的书生8 分钟前
ELK 学习总结
linux
l1t15 分钟前
在aarch64机器上安装clang来生成codonjit python模块
开发语言·python
OYangxf20 分钟前
对TinyRedis中主从复制的理解
运维·服务器
Irene199123 分钟前
大数据开发面试常问的 Linux 命令 总结
大数据·linux
銳昊城24 分钟前
项目六: 配置与管理DNS服务器(2) C2
运维·服务器
辰尘_星启29 分钟前
【Linux】Python Socket编程指南
linux·python·socket·系统·通信
谙弆悕博士31 分钟前
快速学C语言——第19章:C语言常用开发库
c语言·开发语言·算法·业界资讯·常用函数