[Linux]多线程编程

news2024/10/5 17:26:23

[Linux]多线程编程

文章目录

  • [Linux]多线程编程
    • pthread_create函数
    • pthread_join函数
    • pthread_exit函数
    • pthread_cancel函数
    • pthread_self函数
    • pthread_detach函数
    • 理解线程库和线程id

Linux操作系统下,并没有真正意义上的线程,而是由进程中的轻量级进程(LWP)模拟的线程,因此Linux操作系统中只会提供进程操作的系统接口。但是为了用户操作方便,Linux操作系统提供了用户级的原生线程库,原生线程库将系统接口进行封装,让用户可以像使用操作真正的线程一样进行线程操作,另外由于使用的是原生线程库,编译代码时需要指明线程库进行链接。

pthread_create函数

pthread_create函数用于创建线程。

//pthread_create函数所在的头文件和函数声明
#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
  • **thread参数:**返回线程ID

  • **attr参数:**设置线程的属性,attr为NULL表示使用默认属性

  • start_routine参数:是个函数地址,线程启动后要执行的函数

  • **arg参数:**传给线程启动函数的参数

  • **返回值:**成功返回0,失败返回错误码。(由于线程共用同一个地址空间,因此不采用设置全局变量errno的方式记录错误码)

编写如下代码进行测试:

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

using namespace std;

void *thread_run(void *args)
{
    while(true)
    {
        cout << "new pthread running" << endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    pthread_t t;
    int n = pthread_create(&t, nullptr, thread_run, nullptr);
    if (n!=0) cerr << "new thread error" << endl; 

    while(true)
    {
        cout << "main pthread running, new pthread id: " << t << endl;
        sleep(1);
    }
    return 0;
}

编译代码并运行查看结果:

pthreadTest1

另外,可以使用Linux操作系统的指令ps -aL | head -1 && ps -aL | grep 进程名查看线程:

pthreadTest2

其中LWPid和pid相同的是主线程,其余的是新线程。

pthread_join函数

和进程类似,Linux操作系统下线程退出后,也要进行线程等待回收新线程,因此提供了pthread_join函数。

//pthread_join函数所在的头文件和函数声明
 #include <pthread.h>

int pthread_join(pthread_t thread, void **retval);
  • thread参数: 要等待并回收的新线程id。(pthread_create函数创建新线程时的输出型参数thread)
  • retval参数: 作为输出型参数接收线程退出的返回值。
  • **返回值:**成功返回0,失败返回错误码。
  • 如果线程被取消了,retval参数会接收到PTHREAD_CANCELED ((void *) -1)

注意: 由于线程异常会产生信号直接导致进程终止,因此线程等待回收时不需要考虑异常情况检测。

编写如下代码进行测试:

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

#define NUM 10

using namespace std;

void *thread_run(void *args)
{
    while(true)
    {
        cout << "new pthread running" << endl;
        sleep(4);
        break;
    }
    return (void*)0;//返回值为0的数据
}

int main()
{
    pthread_t tid[NUM];
    for (int i = 0; i < NUM; i++)
    {
        int n = pthread_create(tid+i, nullptr, thread_run, nullptr);
        if (n!=0) cerr << "new thread error, thread-" << i << endl; 
    }

    void *ret = nullptr;
    for (int i = 0; i < NUM; i++)
    {
        int m = pthread_join(tid[i], &ret);//等待回收新线程
        if (m!=0) cerr << "new thread join error, thread-" << i << endl; 
        cout << "thread-" << i << "quit, ret: " << (uint64_t)ret << endl;//打印新线程退出返回值
    }

    cout << "all thread quit" << endl;//打印说明所有线程退出

    return 0;
}

编译代码并运行查看结果,在进程执行时采用指令while :; do ps -aL | head -1 && ps -aL | grep 进程名; sleep 1; done检测进程:

pthreadTest3

可以看到线程退出return返回了返回值为0的数据,主线程调用pthread_join能够成功接收返回值。

pthread_exit函数

Linux操作系统下线程退出的方式:

  1. 线程执行函数结束,return返回
  2. 调用phread_exit函数退出线程

注意: 无论是线程执行函数结束,return返回还是调用phread_exit函数退出线程,最终都会给主线程返回一个void *类型的返回值。

//pthread_exit函数所在的头文件和函数声明
#include <pthread.h>

void pthread_exit(void *retval);
  • retval参数: 作为线程终止的返回值返回给主线程。

编写如下代码进行测试:

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

#define NUM 10

using namespace std;

void *thread_run(void *args)
{
    while(true)
    {
        cout << "new pthread running" << endl;
        sleep(4);
        pthread_exit((void*)1);
    }
}

int main()
{
    pthread_t tid[NUM];
    for (int i = 0; i < NUM; i++)
    {
        int n = pthread_create(tid+i, nullptr, thread_run, nullptr);
        if (n!=0) cerr << "new thread error, thread-" << i << endl; 
    }

    void *ret = nullptr;
    for (int i = 0; i < NUM; i++)
    {
        int m = pthread_join(tid[i], &ret);//等待回收新线程
        if (m!=0) cerr << "new thread join error, thread-" << i << endl; 
        cout << "thread-" << i << "quit, ret: " << (uint64_t)ret << endl;//打印新线程退出返回值
    }

    cout << "all thread quit" << endl;//打印说明所有线程退出

    return 0;
}

编译代码并运行查看结果:

pthreadTest4

可以看到线程退出phread_exit返回了值为1的数据,主线程调用pthread_join能够成功接收返回值。

pthread_cancel函数

pthread_cancel函数能够将正在运行的线程取消。

//pthread_cancel函数所在的头文件和函数声明
#include <pthread.h>

int pthread_cancel(pthread_t thread);
  • thread参数: 要取消的线程id。
  • **返回值:**成功返回0,失败返回错误码。

编写如下代码进行测试:

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

using namespace std;

void *thread_run(void *args)
{
    while (true)//新线程死循环执行代码
    {
        cout << "new thread running" << endl;
        sleep(1);
    }
    pthread_exit((void *)11);
}

int main()
{
    pthread_t t;
    pthread_create(&t, nullptr, thread_run, nullptr);

    int cnt = 3;
    while (true)
    {
        sleep(1);
        if ((cnt--) == 0)
        {
            pthread_cancel(t);//取消新线程
            break;
        }
    }
    sleep(2);
    void *ret = nullptr;
    pthread_join(t, &ret);//等待回收新线程
    cout << "new thread quit " << "ret: " << (int64_t)ret << endl;
    return 0;
}

编译代码并运行查看结果:

pthreadTest5

死循环的新线程被主线程取消了,新线程被取消后,主线程pthread_join函数接收到的是PTHREAD_CANCELED ((void *) -1)

pthread_self函数

pthread_self函数用于获取当前线程的线程id。

//pthread_self函数所在的头文件和函数声明
#include <pthread.h>

pthread_t pthread_self(void);
  • 返回值: 返回调用线程的线程id。

编写如下代码进行测试:

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

using namespace std;


void *thread_run(void *args)
{
    pthread_t tid = pthread_self();
    cout << "i am new thread, my thread id: " << tid << endl;
    return nullptr;
}

int main()
{
    pthread_t t;
    pthread_create(&t, nullptr, thread_run, nullptr);
    pthread_join(t, nullptr);
    cout << "new thread id: " << t << endl;
    return 0;
}

编译代码并运行查看结果:

image-20230923174750463

pthread_detach函数

默认情况下,新创建的线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。如果我们将线程分离,当线程退出时,自动释放线程资源。Linux操作系统下提供了pthread_detach函数用于分离线程。

//pthread_detach函数所在的头文件和函数声明
#include <pthread.h>

int pthread_detach(pthread_t thread);
  • thread参数: 要分离的线程id。
  • 线程分离后无法进行pthread_join操作,如果使用了就会报错。

先编写如下代码进行测试:

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

using namespace std;

void *thread_run(void *args)
{
    int cnt = 5;
    while(true)
    {
        cout << (char*)args << " : " << cnt-- << endl;
        if (cnt==0) break;
    }
    return nullptr;
}

int main()
{
    pthread_t t;
    pthread_create(&t, nullptr, thread_run, (void*)"new_thread");
    pthread_detach(t);//分离线程

    int n = pthread_join(t, nullptr);//线程等待
    if (n != 0)
    {
        cerr << "pthread_join error: " << n << " : " << strerror(n) << endl; 
    }
    return 0;
}

编译代码并运行查看结果:

image-20230923193701650

由于主线程和新线程的调度问题,造成了如上两种情况,但是无论哪种情况,新线程分离后,在进行等待操作就会报错。

再编写如下代码进行测试:

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

using namespace std;

void *thread_run(void *args)
{
    pthread_detach(pthread_self());//线程分离
    int cnt = 5;
    while(true)
    {
        cout << (char*)args << " : " << cnt-- << endl;
        sleep(1);
        if (cnt==0) break;
    }
    return nullptr;
}

int main()
{
    pthread_t t;
    pthread_create(&t, nullptr, thread_run, (void*)"new_thread");
    int n = pthread_join(t, nullptr);//线程等待
    if (n != 0)
    {
        cerr << "pthread_join error: " << n << " : " << strerror(n) << endl; 
    }
    return 0;
}

编译代码并运行查看结果:

pthreadTest6

主线程先进行等待,新线程后进行分离,就会造成如上这种即使线程分离了等待操作也未报错。因为在主线程进行等待操作时检测新线程未分离,就直接进入阻塞等待新线程的状态了,因此不会报错。

理解线程库和线程id

Linux操作系统下,没有真正的线程,而是用轻量级进程模拟的线程,因此Linux操作系统只能以轻量级进程的方式进行管理,而不能以线程的方式管理,而线程库要给用户提供线程相关的各种各样的操作,线程库就要承担一部分操作系统不具有的线程管理操作,因此用于调用线程库创建线程时,线程库就要创建对应的数据结构记录线程的属性以用于管理线程,在后续调用线程库操作线程时,线程库就会利用之前创建的记录属性的结构和封装的一些系统接口来实现线程操作。

image-20230923203756664

线程库在组织线程管理结构时,会将其线性的记录在进程地址空间上,而线程管理结构在进程地址空间上的首地址就是线程库提供的线程id。

image-20230923204156954

在线程库提供的线程管理结构中,存在一部分空间称为线程栈,线程栈是每个新线程私有的栈,新线程会将创建的临时变量存在线程栈中,将每个线程的数据分离开,以便进行数据的管理,而主线程使用的是进程地址空间中的栈结构。

说明: 在Linux操作系统下,C++提供的线程操作都是对原生线程库的封装。

编写如下代码进行测试:

#include <iostream>
#include <unistd.h>
#include <thread>

using namespace std;

void run1()
{
    while(true)
    {
        cout << "thread 1" << endl;
        sleep(1);
    }
}
void run2()
{
    while(true)
    {
        cout << "thread 2" << endl;
        sleep(1);
    }
}
void run3()
{
    while(true)
    {
        cout << "thread 3" << endl;
        sleep(1);
    }
}


int main()
{
    thread th1(run1);
    thread th2(run2);
    thread th3(run3);

    th1.join();
    th2.join();
    th3.join();

    return 0;
}

在编译时不加-lpthread选项并运行程序结果如下:

image-20230923205821115

由于C++的线程操作是封装原生线程库得来的,如果编译时不链接原生线程库,在程序执行时,操作系统会无法正确的加载动态库到内存中,会导致程序无法正常执行。

补充: 线程管理结构中线程局部存储用于存储线程相关的全局变量、线程上下文信息、隔离敏感数据。

image-20230927102412819

编写如下代码进行测试:

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

using namespace std;

__thread int g_val = 100;//__thread将数据以线程全局变量的形式创建

void *thread_run(void *args)
{
    char* tname = static_cast<char*>(args);
    int cnt = 5;
    while (true)
    {
        cout << tname << ":" << (u_int64_t)cnt << ",g_val: " << g_val << ",&g_val: " << &g_val << endl;
        if ((cnt--)==0)
            break;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    pthread_t tid1;
    pthread_t tid2;
    pthread_t tid3;
    pthread_create(&tid1, nullptr, thread_run, (void*)"thread1");
    pthread_create(&tid2, nullptr, thread_run, (void*)"thread2");
    pthread_create(&tid3, nullptr, thread_run, (void*)"thread3");
    pthread_join(tid1, nullptr);
    pthread_join(tid2, nullptr);
    pthread_join(tid3, nullptr);
    return 0;
}

编译代码并运行查看结果:

image-20230927102959242

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

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

相关文章

vue3 踩坑记(汇总)

1、在 vue3 中&#xff0c;GET 请求接口时&#xff0c;传入一个数组&#xff0c;默认是以“xxx[]: 1, 2, 3”的形式传递的&#xff0c;报错&#xff1a;“400 Bad Request” 解决方案&#xff1a; 传参时&#xff0c;需要将数组字符串化&#xff0c;比如&#xff1a;ids: sele…

二维码智慧门牌管理系统:提升社会治理效率的利器

文章目录 前言一、技术背景与特点二、数据准确性和一致性三、综合服务平台四、应用领域 前言 在当今科技不断发展的时代&#xff0c;我们的生活正逐渐数字化和智能化。近期&#xff0c;一种名为“二维码智慧门牌管理系统”的新型技术引起广泛关注。这一系统的出现不仅为我们的…

软件测试之银行测试,银行测试YYDS

为什么要做金融类软件测试 举个例子&#xff0c;比如银行的软件测试工程师&#xff0c;横向和互联网公司的测试人员比较来说&#xff0c;工资比较稳定&#xff0c;加班很少甚至没有&#xff0c;业务稳定。 实在是测试类岗位中的香饽饽&#xff01; 同时&#xff0c;我也准备了…

如何使用ArcGIS Pro制作标准地图样式国界

相信大家都浏览过标准地图服务提供的标准地图&#xff0c;不知道你有没有想过尝试制作里面的国界&#xff0c;这里为大家介绍一下制作方法&#xff0c;希望能对你有所帮助。 制作已定国界 在地图数据内&#xff0c;国界分为已定国界、未定国界和海岸线&#xff0c;我们先对已定…

一文详解:什么是进销存管理系统?2023年top10进销存管理系统大推荐!

进销存管理系统是什么&#xff1f;进销存管理系统的优势在哪里&#xff1f;进销存管理系统都能为企业提供什么&#xff1f;有哪些便宜适合的进销存管理系统&#xff1f;本文将带大家深入浅出的聊聊进销存管理系统&#xff0c;并且为大家提供2023年十大进销存管理系统大盘点&…

企业简化客户服务的5种方法

在现代商业中&#xff0c;提供优质客户服务是企业能否成功的关键所在。为了满足客户的需求&#xff0c;企业需要保证客户服务的质量和效率。而许多公司却发现&#xff0c;随着公司的发展&#xff0c;客户服务的过程变得越来越复杂。许多企业陷入了自己制造的困境&#xff0c;面…

简易磁盘自动监控服务

本文旨在利用crontab定时任务(脚本请参考附件)来监控单个服务节点上所有磁盘使用情况&#xff0c;一旦超过既定阈值则会通过邮件形式告警相关利益人及时介入处理。 1. 开启SMTP服务 为了能够成功接收告警信息&#xff0c;需要邮件接收客户都安开启SMTP服务。简要流程请参考下…

燃尽图是什么?如何用它提升敏捷项目流程?

**敏捷项目管理**的核心是透明度和持续改进。燃尽图是轻松实现这两点的秘密武器。这种动态的可视化工具能有效地说明团队在一段时间内的进展情况&#xff0c;突出显示剩余的工作&#xff0c;并揭示你的团队是否在实现目标的正轨上。 敏捷项目管理中的燃尽图 燃尽图是敏捷项目…

【git入门教程--基于gitee】

1.git 下载安装 首先下载windows版本的git安装包 https://git-scm.com/download/win 我这里选择64位 windows版本&#xff0c;大部分人用的也是这个版本。安装过程很简单&#xff0c;基本都是下一步再下一步。 2.用户配置 git安装完成之后&#xff0c;在电脑文件夹的任意位…

python程序主动退出进程的方式:五种方式总有一种适合你

一、使用os.kill() os.kill()是一种向进程发送信号的方法&#xff0c;可以用来强制结束一个进程的运行。如果你的程序中包含有线程&#xff0c;用这种方式绝对没错&#xff01;当使用os.kill()方法结束一个进程时&#xff0c;需要指定该进程的PID&#xff08;进程号&#xff0…

【办公自动化】用Python将PDF文件转存为图片(文末送书)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

【Java SE】反射与枚举

目录 ♫反射 ♪什么是反射 ♪与反射相关的类 ♪什么是Class类 ♪获取Class类 ♪class类的常用方法 ♪反射的使用 ♪反射私有方法 ♪反射的优缺点 ♫枚举 ♪什么是枚举 ♪枚举的常用方法 ♪枚举的构造方法 ♫枚举与反射 ♫反射 ♪什么是反射 Java反射是Java语言的一…

【VUE复习·1】单向数据绑定v-bind;双向数据绑定v-model

总览 1.单向数据绑定&#xff1a;v-bind 2.双向数据绑定&#xff1a;v-model 一、v-bind 单向数据绑定 1.图解 data 中的值能够影响页面上的值&#xff0c;但是在页面上更改却不能影响 data 中的值。 2.用法说明 <div><input v-bind:value"name">&l…

ES查询数据的时报错:circuit_breaking_exception[[parent] Data too large

ES配置的官方网站&#xff1a;https://www.elastic.co/guide/en/elasticsearch/reference/7.2/circuit-breaker.html 报错&#xff1a; circuit_breaking_exception[[parent] Data too large, data for [<transport_request>] would be [12318476937/11.2gb], which is…

Vue之ElementUI之动态树+数据表格+分页(项目功能)

目录 前言 一、实现动态树形菜单 1. 配置相应路径 2. 创建组件 3. 配置组件与路由的关系 index.js 4. 编写动态树形菜单 5. 页面效果演示 二、实现数据表格绑定及分页功能 1. 配置相应路径 2. 编写数据表格显示及分页功能代码 BookList.vue 3. 演示效果 总结 前言…

数据结构 - 泛型

目录 前言 1. 什么是泛型? 2. 为什么需要泛型? 引入泛型之前 引入泛型之后 3.泛型类 4.泛型的界限 1.上下界 2.通配符 前言 今天给大家介绍一下泛型的使用 1. 什么是泛型? 一般的类和方法&#xff0c;只能使用具体的类型: 要么是基本类型&#xff0c;要么是自定义…

抖音短视频seo矩阵系统源代码开发系统架构及功能解析

短视频seo源码&#xff0c;短视频seo矩阵系统底层框架上支持了从ai视频混剪&#xff0c;视频批量原创产出&#xff0c;云存储批量视频制作&#xff0c;账号矩阵&#xff0c;视频一键分发&#xff0c;站内实现关键词、短视频批量搜索排名&#xff0c;数据统计分类多功能细节深度…

在多台服务器上运行相同命令(二)、clush

介绍安装配置互信认证参数含义基本使用节点组拷贝文件 介绍 Clush&#xff08;Cluster Shell&#xff09;是一个用于管理和执行集群操作的工具&#xff0c;它允许你在多台远程主机上同时执行命令&#xff0c;以便批量管理服务器。Clush 提供了一种简单而强大的方式来管理大规模…

MySQL:远程连接数据库(2)

环境 两台centos7 其中一台安装MySQL数据库 192.168.254.1 另外一台安装mariadb最小化安装mysql仅供MySQL连接 或者安装完整版mysql也是可以的&#xff0c;只要支持mysql命令即可 192.168.254.2 开始部署 我们如果让那个用户可以远程连接&…

一步解决Android Studio没有提示的问题

问题 我们在使用安卓开发软件的时候常常会遇到没有提示或提示不全的情况&#xff0c;这样会很影响我们的学习效率,接下来我来教大家快速解决这个问题 解决办法 应用完成后点击ok&#xff0c;提示就可以出来了 如果有适配的版本到这里就可以了&#xff0c;没有的话可以继续往下看…