【Linux】线程操作

文章目录

  • 前言
  • 一、线程相关操作函数
    • 1. pthread_create
    • 2. pthread_join
    • 3. pthread_exit
    • 4. pthread_cancel
    • 5. pthread_detach
    • 6. 示例代码


前言

在 Linux 中并不存在真正意义上的线程, 而是通过复用进程的结构来实现的, 叫做轻量级进程. 线程是一个进程内部的一个执行流, 而一个进程最少都有一个线程, 就是该进程本身, 线程在进程内部运行,本质是在进程地址空间内运行, 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流.

进程是资源分配的基本单位, 线程是调度的基本单位.

一、线程相关操作函数

1. pthread_create

头文件: #include <pthread.h>
函数声明: int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

  • 返回值: 成功返回 0, 失败返回 errno.
  • thread: 输出型参数, 返回线程 id.
  • attr: 设置线程的属性, attr 为 NULL 表示使用默认属性.
  • start_routine: 函数指针, 该线程执行的函数.
  • arg: 传递给 start_routine 的参数.

功能: 创建一个线程, 该线程会执行 start_routine 函数.

示例代码(入口函数不传参):

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;void* ThreadRoutine(void* arg)
{for(int i = 0; i < 5; ++i){cout << "Hello World" << endl;sleep(1);}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, ThreadRoutine, nullptr);pthread_join(tid, nullptr);return 0;
}

运行结果:
在编译时会出现如下问题:
在这里插入图片描述
提示我们使用的函数是未定义的, 原因就在于使用了线程库提供的函数没有链接该库, 在 makefile 文件中指明库名即可, 如下:
在这里插入图片描述
此时再编译就没问题了, 运行结果如下:
在这里插入图片描述
程序运行后, 该线程执行流就会去执行其入口函数了, 通过监控脚本观察此时确实有两个线程:
在这里插入图片描述
可以看到监控显示的信息中, 有一个熟悉的字段 PID, 这个 PID 也就是我们跑起来的一个进程, 而字段 LWP(Light Weight Process) 表示轻量级进程, 可以得知在该进程中存在两个执行流, 也就是两个线程, 而其中一个线程的 LWP 和 PID 是一致的, 该线程就是我们的主线程, 此前我们认为的一个进程在 Linux 中其实就是一个只有一个执行流的进程, 所以说在 Linux 中并不存在真正意义上的线程, 在一个进程的内部可以存在多个线程, 并且每个线程都共享所处进程的部分数据, 大致如下图所示:
在这里插入图片描述
整个图表示一个进程, 里面的各个 task_struct 表示一个线程.

示例代码(入口函数传递内置类型):

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;void* ThreadRoutine(void* arg)
{const char* buf = static_cast<const char*>(arg);for(int i = 0; i < 5; ++i){cout << buf << "Hello World" << endl;sleep(1);}return nullptr;
}int main()
{pthread_t tid;char buf[] = "[wcm]:";pthread_create(&tid, nullptr, ThreadRoutine, buf);pthread_join(tid, nullptr);return 0;
}

运行结果:
在这里插入图片描述
入口函数的参数类型为 void*, 表示我们可以传递任意类型的参数, 在入口函数中使用时, 只需要进行类型转换即可, 返回值也是一样的.

2. pthread_join

头文件: #include <pthread.h>
函数声明: int pthread_join(pthread_t thread, void **retval);

  • 返回值: 成功返回 0, 失败返回 errno.
  • thread: 等待的线程 id.
  • retval: 被等待线程的返回值.

功能: 主线程阻塞式的等待回收一个线程.

实例代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;void* ThreadRoutine(void* arg)
{for(int i = 0; i < 5; ++i){cout << "Hello World" << endl;sleep(1);}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, ThreadRoutine, nullptr);pthread_join(tid, nullptr);return 0;
}

如果 pthread_join 的第二个参数设置为空, 表示不接收等待线程的返回值, 要想接收返回值, 代码如下:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;void* ThreadRoutine(void* arg)
{const char* buf = static_cast<const char*>(arg);for(int i = 0; i < 5; ++i){cout << buf << "Hello World" << endl;sleep(1);}return (void*)111;
}int main()
{pthread_t tid;char buf[] = "[wcm]:";pthread_create(&tid, nullptr, ThreadRoutine, buf);void* ret;pthread_join(tid, &ret);int res = reinterpret_cast<uint64_t>(ret);sleep(1);cout << "exit code: " << res << endl;return 0;
}

运行结果:
在这里插入图片描述

3. pthread_exit

头文件: #include <pthread.h>
函数声明: void pthread_exit(void *retval);

  • retval: 线程的返回值.

功能: 如果在一个线程内使用 exit 来终止该线程, 那么该进程所属的整个进程也会被终止, pthread_exit 只会终止当前线程, 并不会影响进程乃至其他任何线程执行流.

示例代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;void* ThreadRoutine(void* arg)
{const char* buf = static_cast<const char*>(arg);for(int i = 0; i < 5; ++i){if(i == 3){exit(1);}cout << buf << "Hello World" << endl;sleep(1);}return nullptr;
}int main()
{pthread_t tid;char buf[] = "[wcm]:";pthread_create(&tid, nullptr, ThreadRoutine, buf);pthread_join(tid, nullptr);sleep(10);cout << "main() quit" << endl;return 0;
}

先看看线程通过 exit 终止会怎样:
在这里插入图片描述
可以看到, 通过 exit 终止的线程不止本身被终止了, 连带着整个进程都被终止了, 原因也很简单, exit 本身就是终止一个进程的, 而多个线程同属一个进程中, 如果该线程的入口函数中通过 exit 终止了, 那么理所应当的整个进程都会被终止, 而 pthread_exit 只会终止调用线程, 并且还可以携带返回值:

#include <iostream>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>
using namespace std;void* ThreadRoutine(void* arg)
{const char* buf = static_cast<const char*>(arg);for(int i = 0; i < 5; ++i){if(i == 3){char* buf = new char[64];snprintf(buf, 64, "i am return val");pthread_exit(buf);}cout << buf << "Hello World" << endl;sleep(1);}return nullptr;
}int main()
{pthread_t tid;char buf[] = "[wcm]:";pthread_create(&tid, nullptr, ThreadRoutine, buf);void* ret;pthread_join(tid, &ret);const char* res = static_cast<const char*>(ret);cout << res << endl;return 0;
}

运行结果:
在这里插入图片描述
成功收到线程终止后的返回值, 并且该线程的终止不会影响其他线程.

4. pthread_cancel

头文件: #include <pthread.h>
函数声明: int pthread_cancel(pthread_t thread);

  • 成功返回 0, 失败返回 errno.
  • thread: 被取消的线程 id.

功能: 取消一个线程的执行.

示例代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;void* ThreadRoutine(void* arg)
{for(int i = 0; i < 10; ++i){printf("[%d]Hello World\n", i + 1);sleep(1);}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, ThreadRoutine, nullptr);for(int i = 0; i < 5; ++i){sleep(1);}pthread_cancel(tid);pthread_join(tid, nullptr);cout << "main thread done" << endl;return 0;
}

运行结果:
在这里插入图片描述
在主线程取消该线程后, 该线程直接结束了.

5. pthread_detach

头文件: #include <pthread.h>
函数声明: int pthread_detach(pthread_t thread);

  • 成功返回 0, 失败返回 errno.
  • thread: 被分离的线程 id.

功能: 分离一个线程, 该线程不需要被等待(join)了.

示例代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;void* ThreadRoutine(void* arg)
{for(int i = 0; i < 10; ++i){printf("[%d]Hello World\n", i + 1);sleep(1);}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, ThreadRoutine, nullptr);pthread_detach(tid);cout << "main thread done" << endl;return 0;
}

6. 示例代码

场景描述: 创建 10 个线程, 每个线程分别执行各自的累加任务, 执行完任务后结束, 最后主线程回收各线程, 程序结束.

代码:

#include <iostream>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>
using namespace std;//枚举退出状态
enum exit_code
{OK,ERROR
};class thread_info
{
public:thread_info(string name, int top, int res = 0, int status = OK):_name(name), _top(top){}
public://输入型参数string _name; //线程名int _top; //该线程要累加到的值//输出型参数int _res; //累加结果int _status; //退出码
};//线程入口函数
void* Routine(void* arg)
{thread_info* ti = static_cast<thread_info*>(arg);cout << "线程" << pthread_self() << "计算中..." << endl;sleep(1);for(int i = 1; i <= ti->_top; ++i){ti->_res += i;}cout << "线程" << pthread_self() << "计算完毕" << endl;sleep(1);pthread_exit(ti);
}int main()
{pthread_t threads[10]; //各线程idfor(int i = 0; i < 10; ++i){char buf[64];snprintf(buf, sizeof(buf), "thread-%d", i);thread_info* ti = new thread_info(buf, 100 + i * 5);pthread_create(threads + i, NULL, Routine, ti);}void* arg;//回收线程for(int i = 0; i < 10; ++i){pthread_join(threads[i], &arg);thread_info* res = static_cast<thread_info*>(arg);printf("线程%d已回收,其累加范围是[1,%d],累加结果:%d,退出码:%d\n", threads[i], res->_top, res->_res, res->_status);delete res;}cout << "线程回收完毕" << endl; return 0;
}

运行结果:
在这里插入图片描述

程序运行起来同时运行的监控脚本:
在这里插入图片描述

从结果可以看出, 确实创建了 10 个线程来执行累加结果, 而最后也成功回收了各线程, 在创建线程时, 给入口函数传参不仅可以传递基础类型, 也可以传递复杂的结构类型, 代码中也是传递了一个自定义的类当作参数, 而从执行顺序可以得知, 线程的执行顺序是不确定的, 在监控脚本中可以看到运行的线程, 其中 PID 是整个进程的 PID, 因为一个进程内可以存在很多个执行流, 所以这些线程执行流的 PID 都是一样的, 而 LWP 则是每个线程都不同的.

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

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

相关文章

521源码-免费游戏源码下载-闯梦江湖Q萌复古全网通手游服务端H5全攻略

闯梦江湖H5&#xff1a;Q萌复古全网通手游服务端全攻略 一、概述 闯梦江湖H5 是一款结合Q萌画风与复古情怀的全网通H5手游。我们为您提供了最新打包的Windows服务端&#xff0c;并附带了通用视频架设教程和GM网页授权后台工具&#xff0c;让您轻松搭建并管理自己的游戏世界。 …

Kubernetes中的节点选择方法

在Kubernetes集群中&#xff0c;节点选择是一个重要的环节&#xff0c;它决定了Pod将被调度到哪个节点上运行。Kubernetes提供了多种节点选择的方法&#xff0c;以满足不同的部署需求和资源优化。本文将介绍Kubernetes中的几种节点选择方法&#xff0c;并附带相关代码示例。 目…

python技巧梳理

背景 在开发中&#xff0c;经常会遇到&#xff0c;同时存在多个值&#xff0c;依次判断上述值&#xff0c;选择第一个非空、True的值作为整个表达式的值进行返回&#xff0c;这个时候会用到or这个关键词&#xff0c;下面讲一下用法。 方法 value1 None value2 0 value3 H…

斯洛文尼亚普利雅玛城堡:吉尼斯世界纪录认证的世界最大溶洞城堡

除了著名的波斯托伊纳溶洞&#xff08;Postojna Cave&#xff09;&#xff0c;普利雅玛城堡&#xff08;Predjama Castle&#xff09;也是波斯托伊纳洞穴公园&#xff08;Postojna Cave Park&#xff09;不容错过的景点之一。这座城堡坐落在斯洛文尼亚&#xff08;Slovenia&…

【编译原理】LR(0)分析

一、实验目的 LR(0)分析法是一种移进归约过程&#xff0c;能根据当前分析栈中的符号串&#xff0c;同时也不用向右查看输入串的符号就可唯一确定分析器的动作。通过对给定的文法构造LR(0)分析表和实现某个符号串的分析掌握LR(0)分析法的基本思想。 二、实验要求 实现LR(0)分…

用(华为)三层交换技术解决不同vlan间通信问题

用三层交换技术解决不同vlan间通信问题 一、网络拓扑&#xff1a; 二、配置思路&#xff1a;自下而上配置 1.PC端配置基本IP信息包括网关 2.接入交换机S1上划分三个vlan&#xff0c;分别是VLAN 10 VLAN 20 VLAN 30 并且将对应的接口加入指定的vlan 3.给接入交换机配置trunk链路…

IT技术 | 电脑蓝屏修复记录DRIVER_IRQL_NOT_LESS_OR_EQUAL

我的台式机是iMac 2015年的&#xff0c;硬盘是机械的&#xff0c;时间久了运行越来越慢。后来对苹果系统失去了兴趣&#xff0c;想换回windows&#xff0c;且想换固态硬盘&#xff0c;就使用winToGo 搞了双系统&#xff0c;在USB外接移动固态硬盘上安装了win10系统。 最近&…

C++ 虚函数和纯虚函数

虚函数 #include <iostream> using namespace std; class Vehicle //交通工具 { public:void run() const{cout << "run a vehicle. " << endl;} }; class Car : public Vehicle //汽车 { public:void run() const{cout << "run a c…

Android studio版本和Android gradle plugin版本对应表

1.Android studio 版本的升级&#xff0c;一个方面上看主要是升级对AGP最高版本的支持 2.那为什么AGP要出高版本呢&#xff0c;主要支持高版本的API&#xff0c;真是一环扣一环

将YOLO格式数据集转换为YOLO-Mamba等特定工具指定的数据集格式

目录 克隆Github YOLO-Mamba源码YOLO-Mamba数据集格式下载的公开数据集目录边界框坐标文件类别标签文件 数据集格式转换代码转换格式的效果展示 今天为大家解析YOLO-Mamba这篇论文开源的代码&#xff0c;首先讲解YOLO格式数据集转换为YOLO-Mamba等特定工具指定的数据集格式的操…

GoogLeNet论文解读—Going deeper with convolutions2015(V1)

GoogLeNet论文解读—Going deeper with convolutions2015 说明&#xff1a;本文只解读GooleNet的14年参赛的v1版本&#xff0c;之后的改进版本可能在日后的学习中继续更新 研究背景 更深的卷积神经网络 认识数据集&#xff1a;ImageNet的大规模图像识别挑战赛 LSVRC-2014&…

Java面试八股之Thread类中的yeild方法有什么作用

Thread类中的yeild方法有什么作用 谦让机制&#xff1a;Thread.yield()方法主要用于实现线程间的礼让或谦让机制。当某个线程执行到yield()方法时&#xff0c;它会主动放弃当前已获得的CPU执行权&#xff0c;从运行状态&#xff08;Running&#xff09;转变为可运行状态&#…

快速失败“fail-fast”和安全失败“fail-safe”

目录 快速失败——fail-fast 异常原因 正常原因 安全失败“fail-safe” 快速失败——fail-fast java的快速失败机制是java集合框架中的一种错误检测机制&#xff0c;当多个线程对集合中的内容进行修改时可能就会抛出ConcurrentModificationException异常。不仅仅在多线程状态…

网络安全等级保护2.0(等保)是什么

等保的全称是信息安全等级保护&#xff0c;是《网络安全法》规定的必须强制执行的&#xff0c;保障公民、社会、国家利益的重要工作。 通俗来讲就是&#xff1a;公司或者单位因为要用互联网&#xff0c;但是网上有坏人&#xff0c;我们不仅要防御外部坏人&#xff0c;还要看看…

2016届蓝桥杯大赛软件类国赛Java大学B组 愤怒小鸟 数学模拟

注意开浮点数 ​​​​ import java.util.Scanner;public class Main {static Scanner scnew Scanner(System.in);public static void main(String[] args) {double t0;int cnt0;double distance1000;while(distance>1){//相撞时间tdistance/60.0;distance-t*20;cnt;}Syste…

Text Control 控件 中 Service Pack 3:MailMerge 支持 SVG 图像

图像的合并方式与报告模板中的合并字段相同。占位符在设计时添加&#xff0c;并与文件、数据库或内存中的数据合并。可以将图像对象添加到具有指定名称的模板中。数据列必须包含字节数组形式的二进制图像数据、System.Drawing.Image 类型的对象、文件名、十六进制或 Base64 编码…

【Linux进程篇】父子进程fork函数||进程生死轮回状态||僵尸进程与孤儿进程

W...Y的主页 &#x1f60a; 代码仓库分享&#x1f495; 前言&#xff1a;上篇文章中我们认识了进程&#xff0c;可执行程序在内存中加载运行被称作进程&#xff0c;而操作系统是通过给每一个可执行程序创建一个PCB来管理进程的。并且学习了一些查看进程的指令&#xff0c;认识…

香橙派AIpro开发板开箱体验,国产AI计算力实现可控

一、引言 1. 嵌入式AI技术背景与发展趋势 近年来&#xff0c;随着人工智能技术的飞速发展&#xff0c;嵌入式AI技术在各个领域得到了广泛应用。作为智能设备的核心部件&#xff0c;嵌入式AI开发板为开发者提供了高效、便捷的开发环境&#xff0c;推动了智能设备的不断创新和进步…

新闻稿推广策略有哪些?建议收藏

新闻稿推广是一种有效的公关工具&#xff0c;它可以帮助企业或组织传递信息、提升品牌知名度、建立权威形象&#xff0c;并最终促进销售。新闻稿推广策略有哪些&#xff1f;接下来伯乐网络传媒就来给大家分享一下。 确定目标受众&#xff1a;在撰写新闻稿之前&#xff0c;明确你…