【线程】线程的控制

news2024/9/21 22:31:02

本文重点:理解线程控制的接口

前言

内核中是没有很明确线程的概念的,只有轻量级进程的概念,不会提供直接给我们线程的系统调用,而会给我们提供轻量级进程的系统调用。我们用户是需要线程的接口的,在应用层,人们封装了轻量级进程的系统调用,为用户直接提供线程的接口,这个封装的就是线程库pthread库,这个是第三方库,几乎所有的Linux系统都会自带这个库,当我们进行编译链接时,要指定这个库

线程的创建

函数pthread_create

参数:

thread:输出型参数,返回线程ID
attr:设置线程的属性,attr为nullptr表示使用默认属性
start_routine:是个函数指针,线程启动后要执行的函数
arg:传给线程启动函数的参数,线程被创建成功,新线程回调线程函数的时候需要参数,这个参数是给线程函数传递的

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

传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。
pthreads函数出错时不会设置全局变量errno(而大部分其他线程函数会这样做)。而是将错误代码通过返回值返回
pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误,建议通过返回值判定,因为读取返回值要比读取线程内的errno变量的开销更小

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

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>

using namespace std;

void* threadroutine(void * arg)
{
    const char* name=(const char*)arg;
    while(true)
    {
        cout<<name<<",pid: "<<getpid()<<endl;
        sleep(2);
    }
}


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

    while(true)
    {
        cout<<"main thread,pid: "<<getpid()<<endl;
        sleep(1);
    }
    return 0;
}

发现它们的pid是一样的

ps  -aL   查看系统轻量级进程 

LWP(light weight process):线程ID,这个线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。

我们发现一个PID和LWP是一样的,那这个就是主线程,其他就是创建出来的线程 

线程组内的第一个线程,在用户态被称为主线程(main thread),内核在创建第一个线程时,会将线程组的ID的值设置成第一个线程的线程ID,既主线程的进程描述符。所以线程组内存在一个线程ID等于进程ID,而该线程即为线程组的主线程

至于线程组其他线程的ID则有内核负责分配,其线程组ID总是和主线程的线程组ID一致,无论是主线程直接创建线程,还是创建出来的线程再次创建线程,都是这样

强调一点,线程和进程不一样,进程有父进程的概念,但在线程组里面,所有的线程都是对等关系

线程的终止

如果需要只终止某个线程而不终止整个进程,可以有三种方法:
1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
2. 线程可以调用pthread_ exit终止自己。
3. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。

注意:以前的进程终止函数exit是用来终止进程的

函数pthread_exit

参数和线程函数的返回值类型是一样的都是void*,这就和return终止线程差不多了

需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

函数pthread_cancel

取消执行中的线程

线程的等待

在进程里有个进程等待,父进程等待子进程,是为了防止僵尸进程,回收子进程,防止内存泄漏

为什么需要线程等待?已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。创建新的线程不会复用刚才退出线程的地址空间。那么线程中也要有线程等待,如果没有,那么也会有类似于僵尸的状态

函数pthread_join

函数的作用:等待指定线程,可以获取这个线程函数的返回值void*(终止状态)

参数:
thread:线程ID
value_ptr:它指向一个指针,就是指向线程创建时,调用的线程函数的返回值void*
返回值:成功返回0;失败返回错误码

调用该函数的线程将阻塞等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:
1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
2. 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_CANCELED(一个宏常数,就是-1)。
3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传pthread_exit的参数。
4. 如果对thread线程的终止状态不感兴趣,可以传nullptr给value_ ptr参数。 

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>

using namespace std;

void* threadroutine(void * arg)
{
    const char* name=(const char*)arg;
    int cnt=5;
    while(true)
    {
        cout<<name<<",pid: "<<getpid()<<endl;
        sleep(1);
        cnt--;
        if(cnt==0) break;
    }
    return (void*)1;//我的当前平台,地址是64位,8字节,而int是4字节,不进行强转会导致混乱
}


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

    void * retval;
    pthread_join(tid,&retval);// main thread等待的时候,默认是阻塞等待的!

    cout << "main thread quit ..., ret: " << (long long int)retval << endl;
  
    return 0;
}

为什么我们在这里join的时候不考虑异常呢??因为做不到,线程异常了整个进程都会挂掉。线程的健壮性低

线程的分离

上面的线程等待,只能阻塞等待,那如果不想阻塞怎么办,并且不关心线程的返回值,那就让线程分离,线程分离之后,主线程就不管了,主线程就干自己的事了,自己的资源自己释放回收了,虽然线程分离了,但是还是属于这个进程的

可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离

joinable和分离是冲突的,一个线程不能既是joinable又是分离的,也就是说,线程分离了,主线程就不用等待回收线程了

#include<iostream>
#include<cstring>
#include<unistd.h>
#include<pthread.h>
void* PthreadRoutine(void* args)
{
    int i=0;
    pthread_detach(pthread_self());
    while(i<3)
    {
        cout<<"child thread ,pid: "<<getpid()<<endl;
        i++;
        sleep(1);
    }
    return nullptr;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,PthreadRoutine,nullptr);
    sleep(1);//确保分离成功

    int n=pthread_join(tid,nullptr);
    printf("n = %d, who = 0x%x, why: %s\n", n, tid, strerror(n));

    return 0;

}

线程ID及进程地址空间布局

pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。
线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID

函数pthread_self

函数clone 

这个函数clone是系统调用,用来创建轻量级进程的,前言说的pthread库的底层就是用这个函数封装的

使用线程库,是要加载到内存的,它是动态库,所以会在地址空间的共享区

线程的概念是库给我们维护的,线程库注定要维护多个线程,线程库要管理这些线程,先描述再组织,每个线程都会有一个库级别的结构体tcb,这个结构体的起始地址就是线程的id 

除了主线程外,所有的其他线程的独立栈,都在共享区,具体来讲,是在pthread库中,tid指向用户的tcb。全局变量和堆区都是线程共享的,栈不是,每个线程都有自己的独立栈

目前,我们的原生线程,pthread库,原生线程库,C++11 语言本身也已经支持多线程了 vs 原生线程库。其实C++的多线程底层封装的就是Linux系统下的原生线程库,为什么C++移值性高,就是因为C++的代码在不同平台上都可以跑,在Linux下跑多线程底层就是Linux的原生线程库,在Windows跑,底层就是Windows的原生线程库,C++语音在底层设计时,不同的系统有不同的设计,但是在上层看来都一样

#include <iostream>
#include <thread>

using namespace std;

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

int main()
{
    thread t1(threadrun);

    t1.join();

    return 0;
}

多线程的创建

1.创建多线程

#include<iostream>
#include<string>
#include<vector>
#include<unistd.h>
#include<pthread.h>

using namespace std;

#define NUM 4

class ThreadData
{
public:
    ThreadData(int number)
    {
        _threadname="thread-"+to_string(number);
    }
public:
    string _threadname;
};

string toHex(pthread_t tid)
{
    char buf[128];
    snprintf(buf,sizeof(buf),"0x%x",tid);
    return buf;

}
void* ThreadRoutine(void* args)
{
    ThreadData* td=static_cast<ThreadData*>(args);
    string tid=toHex(pthread_self());
    pid_t pid=getpid();
    int i=0;
    while(i<3)
    {
        cout<<td->_threadname<<" pid: "<<pid<<" tid: "<<tid<<endl;
        i++;
        sleep(1);
    }
    delete td;
    return nullptr;
}
int main()
{
    vector<pthread_t> tids;

    //创建多线程
    for(int i=1;i<=NUM;i++)
    {
        pthread_t tid;
        ThreadData* td=new ThreadData(i);
        pthread_create(&tid,nullptr,ThreadRoutine,td);
        tids.push_back(tid);
        sleep(1);
    }

    //等待多线程
    for(auto tid:tids)
    {
        pthread_join(tid,nullptr);
    }
    cout<<"main thread wait success"<<endl;

    return 0;
}

上面代码在主线程开辟的堆空间传递给了子线程,子线程还可以利用,说明堆空间是被所有线程共享的

2.验证全局变量共享

在上面代码上加个全局变量g_val,在子线程打印他的地址

#include<iostream>
#include<string>
#include<vector>
#include<unistd.h>
#include<pthread.h>


using namespace std;

#define NUM 4
int g_val=0;//定义一个全局

class ThreadData
{
public:
    ThreadData(int number)
    {
        _threadname="thread-"+to_string(number);
    }
public:
    string _threadname;
};

string toHex(pthread_t tid)
{
    char buf[128];
    snprintf(buf,sizeof(buf),"0x%x",tid);
    return buf;

}
void* ThreadRoutine(void* args)
{
    ThreadData* td=static_cast<ThreadData*>(args);
    string tid=toHex(pthread_self());
    pid_t pid=getpid();
    int i=0;
    while(i<3)
    {
        cout<<td->_threadname<<" pid: "<<pid<<" tid: "<<tid<<",g_val: 
        "<<g_val<<",&g_val: "<<&g_val<<endl;
        i++;
        sleep(1);
    }
    delete td;
    return nullptr;
}
int main()
{
    vector<pthread_t> tids;

    //创建多线程
    for(int i=1;i<=NUM;i++)
    {
        pthread_t tid;
        ThreadData* td=new ThreadData(i);
        pthread_create(&tid,nullptr,ThreadRoutine,td);
        tids.push_back(tid);
        sleep(1);
    }

    //等待多线程
    for(auto tid:tids)
    {
        pthread_join(tid,nullptr);
    }
    cout<<"main thread wait success"<<endl;

    return 0;
}

发现这个全局变量的地址都是一样的,说明全局变量被所有线程共享

3.线程的局部存储

如果线程不想共享全局变量,想要自己私有的全局变量,那么在全局变量加__thread修饰(两个下划线),这个是个编译选项,只能用来定义内置类型,不能修饰自定义类型,那么这个全局变量就不在全局区了,就在线程的栈结构的局部存储中,这个是线程级别的全局变量,也就是在某个线程里面时全局的

 

 地址不一样,并且这个地址挺大的,说明在中间的堆栈之间

4.验证线程具有独立栈结构

在每个线程里面加个变量test_i,打印他的地址

#include<iostream>
#include<string>
#include<vector>
#include<unistd.h>
#include<pthread.h>


using namespace std;

#define NUM 4

class ThreadData
{
public:
    ThreadData(int number)
    {
        _threadname="thread-"+to_string(number);
    }
public:
    string _threadname;
};

string toHex(pthread_t tid)
{
    char buf[128];
    snprintf(buf,sizeof(buf),"0x%x",tid);
    return buf;

}
void* ThreadRoutine(void* args)
{
    ThreadData* td=static_cast<ThreadData*>(args);
    string tid=toHex(pthread_self());
    pid_t pid=getpid();
    int i=0;
    int test_i=0;
    while(i<3)
    {
        cout<<td->_threadname<<" pid: "<<pid<<" tid: "<<tid<<",test_i: 
        "<<test_i<<",&test_i: "<<&test_i<<endl;;
        i++;
        sleep(1);
    }
    delete td;
    return nullptr;
}
int main()
{
    vector<pthread_t> tids;

    //创建多线程
    for(int i=1;i<=NUM;i++)
    {
        pthread_t tid;
        ThreadData* td=new ThreadData(i);
        pthread_create(&tid,nullptr,ThreadRoutine,td);
        tids.push_back(tid);
        sleep(1);
    }

    //等待多线程
    for(auto tid:tids)
    {
        pthread_join(tid,nullptr);
    }
    cout<<"main thread wait success"<<endl;

    return 0;
}

 

发现test_i的地址都不一样,说明它们有独立的栈结构,并不是私有的栈结构,主线程想访问也可以访问的,线程中没有秘密,如果要访问还是可以访问的

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2153467.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java重点学习-总结

十五 总结 https://kdocs.cn/l/crbMWc8xEZda &#xff08;总结全部的精华&#xff09; 1.面试准备 企业筛选简历规则简历编写注意事项(亮点)项目怎么找&#xff0c;学习到什么程度面试过程(表达结构、什么样的心态去找工作) 2.redis 缓存相关(缓存击穿、穿透、雪崩、缓存过期淘…

智能自行车码表:基于2605C语音芯片的创新开发方案

一、开发背景 随着科技的飞速发展和人们对健康生活的追求&#xff0c;自行车骑行已成为一种广受欢迎的绿色出行方式。智能自行车码表作为骑行者的得力助手&#xff0c;不仅记录骑行数据&#xff0c;还逐渐融入了更多智能化功能。然而&#xff0c;传统码表在语音提示、多语种支持…

Science Robotic 内在触觉实现直观的物理人机交互

触觉传感器和电子皮肤是为机器人提供物理交互感的常见设备&#xff0c;但当用于机器人的大面积覆盖时&#xff0c;它们会变得复杂且昂贵。德国宇航中心近期发表的Science Robotics研究工作&#xff0c;使用内部高分辨率关节力扭矩传感器&#xff0c;在机械臂中实现了固有的全身…

linux网络-----传输层

前言 一.传输层&#xff1a; 数据要交接应用层先通过传输层&#xff08;给哪个程序发数据&#xff09; 传输层作用&#xff1a;负责数据能够从发送端传输接收端。对于应用层来说有许多服务&#xff0c;传输层怎么知道把数据发给那个应用服务&#xff1f; 这时就有了端口号&am…

BMC 虚拟i2c访问PCA9545(switch芯片)后面的设备,为什么找不到PCA9545?

1.说明 1.1 背景 无意中看到PCA9545(switch芯片)后面有设备&#xff0c;但是PCA9545设备本身是连接到物理设备i2c上的&#xff0c;然而扫描该物理i2c bus&#xff0c;却找不到该设备。此篇文章主要找一下该原因的。 1.2 参考代码 当前使用的是ast2600芯片&#xff0c;可参考…

Mudslide

作者未提供代码

Qt/C++ TCP调试助手V1.1 新增图像传输与接收功能(附发布版下载链接)

发布版本链接 通过百度网盘分享的文件&#xff1a;TCP调试助手V1.zip&#xff08;含客户端与服务器&#xff09; 链接&#xff1a;https://pan.baidu.com/s/14LTRPChPhYdwp_s6KeyBiA?pwdcedu 提取码&#xff1a;cedu 基于Qt/C实现了一款功能丰富的TCP服务器与客户端调试助手…

HT876 带任意限幅的10.9Wx2高保真音频功放

特点 可任意配置的限幅功能 自由选择音频限制幅度&#xff0c;使输出音频信号限制在固定 失真水平内 内置自动限温控制功能 支持AB类与D类切换 THDN:0.02%(VDD8.4V, RL 4Ω, fIN 1kHz, Po 2x1.0W, BTL) 输出功率(fIN1kHZ,THDN10%) 2x10.9W (VDD9.0V, RL4Ω, BTL) VDD供电范围:2…

【C++】模拟实现二叉搜索(排序)树

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:实战项目集 ⚙️操作环境:Visual Studio 2022 目录 一.了解项目功能 二.逐步实现项目功能模块及其逻辑详解 &#x1f4cc;实现BSTreeNode类模板 &#x1f38f;构造BSTreeNode类成员变量 &#x1f38f;实现BSTreeNode类构…

空间解析几何2:空间中两线段/直线的距离【附MATLAB代码】

目录 理论公式 MATLAB代码 理论公式 MATLAB代码 公式实现 function [dis,P,Q,t1,s1]line2LineDistance(A1,B1,C1,D1) %求两线段的最短距离 % input % A1,B1为线段一的两端点 C1,D1为线段二的两端点 % output % dis,为两线段的最短距离&#xff0c;P,Q为距离最短时在两线段上…

10.2 溪降技术:双重检查

目录 10.2 双重检查概览观看视频课程电子书&#xff1a;双重检查场景场景 1场景 2 个人责任示例 1示例 2 总结 10.2 双重检查 概览 俗话说&#xff1a;“江山易改&#xff0c;本性难移”。在我们开始体验峡谷探险时&#xff0c;培养良好的习惯对我们的进一步发展至关重要。在所…

Spring AOP的应用

目录 1、maven坐标配置与xml头配置 2、代理方式的选择与配置 3、AOP的三种配置方式 3.1、XML模式 3.1.1 创建目标类和方法 3.1.2 创建切面 3.1.3 切面xml配置与表达式说明 3.1.4 单测 3.2 纯注解模式 3.2.1 开启注解相关配置 3.2.2 创建目标类和方法 3.2.3 创建切面…

ChatGPT 4o 使用指南 (9月更新)

首先基础知识还是要介绍得~ 一、模型知识&#xff1a; GPT-4o&#xff1a;最新的版本模型&#xff0c;支持视觉等多模态&#xff0c;OpenAI 文档中已经更新了 GPT-4o 的介绍&#xff1a;128k 上下文&#xff0c;训练截止 2023 年 10 月&#xff08;作为对比&#xff0c;GPT-4…

java之斗地主部分功能的实现

今天我们要实现斗地主中发牌和洗牌这两个功能&#xff0c;该如何去实现呢&#xff1f; 1.创建牌类&#xff1a;52张牌每一张牌包含两个属性:牌的大小和牌的花色。 故我们优先创建一个牌的类(Card)&#xff1a;包含大小和花色。 public class Card { //单张牌的大小及类型/…

20240921在友善之臂的NanoPC-T6开发板上使用Rockchip原厂的Android12适配宸芯的数传模块CX6602N

127|console:/ # uname -a console:/ # ifconfig console:/ # ifconfig -a console:/ # ifconfig -a 130|console:/ # ifconfig usb0 192.168.42.130 console:/ # console:/ # ifconfig console:/ # iperf3 -s & iperf3 -c 192.168.42.130 -i 1 -t 30 20240921在友善之臂的…

828华为云征文|华为云Flexus云服务器X实例之openEuler系统下部署Grav内容管理系统

828华为云征文&#xff5c;华为云Flexus云服务器X实例之openEuler系统下部署Grav内容管理系统 前言一、Flexus云服务器X实例介绍1.1 Flexus云服务器X实例简介1.2 Flexus云服务器X实例特点1.3 Flexus云服务器X实例使用场景 二、Grav介绍2.1 CMS介绍2.2 Grav简介2.3 Grav特点2.4 …

TinyML-On-The-Fly: 实时、低功耗、低成本的微控制器嵌入式设备内计算机视觉技术用于无人机图像分类

这篇论文的标题是《TinyML-On-The-Fly: Real-Time Low-Power and Low-Cost MCU-Embedded On-Device Computer Vision for Aerial Image Classification》&#xff0c;作者是 Riya Samanta, Bidyut Saha, Soumya K. Ghosh&#xff0c;来自印度理工学院克勒格布尔分校。论文主要研…

电子元器件之MOS管,附上几个常用MOS管电路和仿真。

MOS管是一种常用的电子元器件。 1.MOS管的类别 MOSFET简称MOS&#xff0c;是一种绝缘栅型场效应管。按照类别可以分为增强型mos管和耗尽型mos管。 导电沟道的形成方式‌ 增强型MOS管&#xff1a;在没有外加电压时&#xff0c;源极和漏极之间没有导电沟道存在。只有当栅极电…

【玉米田】

题目 代码 #include <bits/stdc.h> using namespace std; typedef long long LL;const int mod 1e8; const int M 1 << 12; LL f[13][M]; int g[13]; vector<int> state; vector<int> p[M]; int n, m; bool check(int x) {return !(x & x <&…

攻防世界---->Windows_Reverse1(补)

做题笔记。 做题回顾。 假设&#xff0c;我们不知道地址随机怎么办&#xff1f;不能动调&#xff0c;只能静态分析。 下载 查壳 upx脱壳。 32ida打开。 动调报错。 重新打开&#xff0c;静态分析。 跟进关键函数。 不明白可以反汇编和汇编一起看。 溯源。 *decode 取值等于 by…