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

embedded/2024/10/19 21:29: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 10for(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 10for(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 中

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


http://www.ppmy.cn/embedded/100408.html

相关文章

ffmpeg6.1集成ffmpeg-gl-transition滤镜

可代安装,有需要可以私信 ffmpeg-gl-transition 是基于 ffmpeg 4.x 进行开发的一个滤镜插件,在高版本上安装会有很多问题,以下是安装步骤,过程中可能会遇到很多报错,每个人的环境不一样,遇到的报错也不一样,但是都有解决办法。以下步骤中如果是在容器中docker 中,如果…

复现XSS漏洞及分析

一、xss是什么&#xff1f; XSS漏洞概述&#xff1a; 跨站脚本攻击XSS(Cross Site Scripting)&#xff0c;为区别层叠样式表(Cascading Style Sheets, CSS)&#xff0c;所以改写为XSS 类型一&#xff1a;反射型 特点&#xff1a; 1、非持久型&#xff0c;不保存到正常服务器…

mysql的安装与初始化

mysql mysql5.7.40下载链接 mysql安装文档 1. 编译安装过程 yum install -y cmake # 安装cmake tar xf mysql-boost-5.7.40.tar.gz cd /root/mysql-5.7.40 cmake -LH # 查看cmake的默认参数&#xff0c;需要进入mysql目录 yum install -y gcc-c.x86_64 yum install -y bis…

饿了么后端登录模块

一、回顾 高并发集群 饿了么后端的登录模块 1、数据库 1. 主从复制(高可用) 2. 传统的主从复制 3. gtids事务型的主从复制 4. 注意 1. server_id唯一 2. 8.x版本需要get_ssl_pub_key 3. 5.x不需要 4. change master to 5. stop | start slave 5. 非交互 import pymy…

Java 2.4 - JVM

一、Java 内存区域详解&#xff08;重点&#xff09; 本篇讨论的是 HotSpot 虚拟机 相比于 C 而言&#xff0c;程序员不需要对每个 new 操作都写对应的 delete / free 操作&#xff0c;这些操作我们会交给虚拟机去做。因此&#xff0c;如果不了解虚拟机的原理&#xff0c;一旦…

代码与优化(4)——MYSQL的连表与子查询

​ mysql连表理解 最简单的情形 user用户表里面 字段 id 在另外一张关联表例如订单表关联过来了 user_id 需要返回下单的用户昵称信息 当后台需要列表&#xff0c;同时需要显示最新的用户信息的时候 如果不使用联表的逻辑查询: 主要解决办法 读取全部的订单信息表&#xff0c;根…

TypeScript 类型注解(二)

一、TypeScript类型约束--对象 对象其实和数组类似&#xff0c;不是限制对象本身的类型&#xff0c;而是对对象属性类型进行限制 结构简化&#xff1a; 对对象做类型限制的好处&#xff1a; 大家都学习过一段时间编程了&#xff0c;会发现咱们经常操作的类型就是对象&#xf…

【论文分享】Graviton: Trusted Execution Environments on GPUs 2018’OSDI

目录 AbstractIntroductioncontributions BackgroundGPUSoftware stackHardwareContext and channel managementCommand submissionProgramming modelInitializationMemory allocationHost-GPU transfersKernel dispatch Sharing Intel SGX Threat ModelOverviewGraviton Archi…