[Linux]多线程编程

[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 10using 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 10using 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.mzph.cn/news/88939.shtml

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

相关文章

Python二级 每周练习题20

练习一: 日期计算器 设计一款日期计算程序&#xff0c;能否实现下面的功能&#xff1a; (1)要求用户分别输入年、月、日&#xff08;分三次输入&#xff09;&#xff1b; (2)程序自动会根据输入的年月日计算出这一天是这一年的第几天&#xff1b; (3)输出格式为&#xff1a;这…

超全超详细的Redis笔记-数据类型及其使用、主从复制、哨兵模式、缓存穿透、击穿、雪崩

文章目录 狂神聊Redis1、Nosql概述1.1、为什么要用Nosql1.2、什么是NoSQL1.3、NoSQL的四大分类 2、Redis 入门2.1、概述2.2、Windows 安装2.3、Linux安装2.4、测试性能2.5、Redis基础知识 3、五大基本数据类型3.1、Redis-Key3.2、String3.3、List3.4、Set3.5、Hash&#xff08;…

成都瀚网科技有限公司:抖店精选联盟怎么用?

抖音精选联盟是抖音电商平台提供的一项服务&#xff0c;旨在为商家提供更多的推广机会和销售渠道。然而&#xff0c;很多人对于如何使用抖店精选联盟以及如何开通这项服务不太了解。本文将为您详细介绍抖店精选联盟的使用和激活流程。 第一节&#xff1a;如何使用抖店精选联盟 …

国密国际SSL双证书解决方案,满足企事业单位国产国密SSL证书要求

近年来&#xff0c;为了摆脱对国外技术和产品的依赖&#xff0c;建设安全的网络环境&#xff0c;以及加强我国对网络信息的安全可控能力&#xff0c;我国推出了国密算法。同时&#xff0c;为保护网络通信信息安全&#xff0c;更高级别的安全加密数字证书—国密SSL证书应运而生。…

容器管理工具 Docker生态架构及部署

目录 一、Docker生态架构 1.1 Docker Containers Are Everywhere 1.2 生态架构 1.2.1 Docker Host 1.2.2 Docker daemon 1.2.3 Registry 1.2.4 Docker client 1.2.5 Image 1.2.6 Container 1.2.7 Docker Dashboard 1.3 Docker版本 二、Docker部署 2.1 使用YUM源部署…

用友U8 CRM客户关系管理任意文件上传漏洞复现【附POC】

文章目录 用友U8 CRM客户关系管理任意文件上传漏洞复现0x01 前言0x02 漏洞描述0x03 影响平台0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现4.访问shell地址 0x06 整改建议 用友U8 CRM客户关系管理任意文件上传漏洞复现 0x01 前言 免责声明&#xff1a;请勿利用文…

【Java 基础篇】Java网络编程实战:P2P文件共享详解

Java网络编程是现代软件开发中不可或缺的一部分&#xff0c;因为它允许不同计算机之间的数据传输和通信。在本篇博客中&#xff0c;我们将深入探讨Java中的P2P文件共享&#xff0c;包括什么是P2P文件共享、如何实现它以及一些相关的重要概念。 什么是P2P文件共享&#xff1f; …

mysql自动删除过期的binlog

一、binlog_expire_logs_seconds 配置项 mysql 8.0使用配置项 binlog_expire_logs_seconds 设置binlog过期时间&#xff0c;单位为秒。 mysql旧版本使用配置项 expire_logs_days 设置binlog过期时间&#xff0c;单位为天&#xff0c;不方便测试。 在 8.0 使用 expire_logs_d…

外包干了3个月,技术退步明显。。。。。

先说一下自己的情况&#xff0c;大专生&#xff0c;17年通过校招进入广州某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试…

Lua函数

--函数--无参无返回值 function F1()print("F1函数") end F1() print("*****************")--有参 function F2(a)print("F2函数"..a) end F2(2) --如果传入参数和函数数量不一致 --不会报错只是补空 F2(1,2) print("*****************&quo…

Wireshark抓包分析ICMP协议

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;对网络安全感兴趣的小伙伴可以关注专栏《网络安全入门到精通》 分析目的&#xff1a;分析ICMP协议的数据格式、报文…

机器人过程自动化(RPA)入门 4. 数据处理

到目前为止,我们已经了解了RPA的基本知识,以及如何使用流程图或序列来组织工作流中的步骤。我们现在了解了UiPath组件,并对UiPath Studio有了全面的了解。我们用几个简单的例子制作了我们的第一个机器人。在我们继续之前,我们应该了解UiPath中的变量和数据操作。它与其他编…

【免费】2023云栖大会门票开抢啦!数量有限,先到先得!

&#x1f3ab; 报名方式&#xff1a;点击链接即可免费报名&#xff01; &#x1f517; 2023云栖大会-领票页 &#x1f4c5; 10月31日-11月2日&#xff0c;让我们齐聚云栖小镇&#xff01;

Unity之NetCode多人网络游戏联机对战教程(3)--NetworkObject组件讲解

文章目录 NetworkObjectAlways Replicate As RootSynchronization TransformActive Scene SynchronizationScene Migration SynchronizationSpawn With ObserversDont Destroy With OwnerAuto Object Parent Sync 后话 NetworkObject 为了复制任何Netcode感知属性或发送/接收R…

3、嵌入式系统的启动过程(BoodLoader)

1、系统启动过程 通电 - > 执行BootLoader - > 加载内核 - > 挂在根文件系统 - > 执行应用程序 Windows的启动过程&#xff1a; 通电 - > 执行BIOS - > 加载WinNT内核 - > 挂在文件系统 - > 执行应用程序 二、嵌入式系统的结构 BootLoader 1、BootL…

DM8归档管理

开启归档 归档的格式&#xff1a; ARCH_NAME_DB_MAGIC[SEQNO]_日期时间.log ARCH_NAME 是在 dmarch.ini中配置的 LOCAL/REMOTE 归档名称 DB_MAGIC 是生成日志的数据库魔数 SEQNO 代表DSC 节点号&#xff0c;日期时间是归档日志文件的创建时间。 eg&#xff1a;ARCHIVE_LOCAL1_…

SpringMVC 学习(二)Hello SpringMVC

3. Hello SpringMVC (1) 新建 maven 模块 springmvc-02-hellomvc (2) 确认依赖的导入 (3) 配置 web.xml <!--web/WEB-INF/web.xml--> <?xml version"1.0" encoding"UTF-8"?> <web-app xmlns"http://xmlns.jcp.org/xml/ns/javaee…

redis部署与管理

目录 一、关系数据库与非关系型数据库&#xff1a; 1. 关系型数据库&#xff1a; 2.非关系型数据库&#xff1a; 二、关系型数据库和非关系型数据库区别&#xff1a; &#xff08;1&#xff09;数据存储方式不同&#xff1a; &#xff08;2&#xff09;扩展方式不同&#xf…

数据结构与算法基础-(3)

&#x1f308;write in front&#x1f308; &#x1f9f8;大家好&#xff0c;我是Aileen&#x1f9f8;.希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流. &#x1f194;本文由Aileen_0v0&#x1f9f8; 原创 CSDN首发&#x1f412; 如…

【计算机网络】DNS原理介绍

文章目录 DNS提供的服务DNS的工作机理DNS查询过程DNS缓存 DNS记录和报文DNS记录DNS报文针对DNS服务的攻击 DNS提供的服务 DNS&#xff0c;即域名系统(Domain Name System) 提供的服务 一种实现从主机名到IP地址转换的目录服务&#xff0c;为Internet上的用户应用程序以及其他…