深入理解Linux中的线程控制:多线程编程的实战技巧
个人主页:chian-ocean
文章专栏-Linux
前言:
POSIX线程(Pthreads) 是一种在 POSIX 标准下定义的线程库,它为多线程编程提供了统一的接口,主要用于 UNIX 和类 UNIX 系统(如 Linux、MacOS 和 BSD 等)。POSIX 线程(Pthreads)允许程序在多个处理器上并行运行,从而提高应用程序的性能,尤其在多核处理器环境中。

监控线程的bash
while :; do ps -aL | head -1 ; ps -aL | grep thread ; sleep 1;done
线程的控制
- 与线程有关的函数构成了⼀个完整的系列,绝⼤多数函数的名字都是以
pthread_打头的 - 要使⽤这些函数库,要通过引⼊头⽂
pthread.h - 链接这些线程函数库时要使⽤编译器命令的“-lpthread”选项
线程的创建(pthread_create)

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine)(void *), void *arg);
参数解析:
pthread_t *thread:指向pthread_t类型的变量,这个变量将存储新线程的 ID。const pthread_attr_t *attr:指向pthread_attr_t类型的指针,它包含了新线程的属性(如栈大小、调度策略等)。如果传入NULL,则使用默认属性。void *(*start_routine)(void *):这是一个指向线程执行函数的指针,该函数接收一个void*类型的参数,并返回void*类型的结果。void *arg:这是传递给start_routine函数的参数,允许你向线程传递数据。
示例:
#include<iostream>
#include<unistd.h>
#include<pthread.h> using namespace std; // 线程函数
void* mythread(void* args)
{// 创建一个循环,子线程会打印 6 次for(int i = 0; i < 6; i++) {sleep(1); // 让线程睡眠 1 秒钟,模拟任务的执行cout << "Child Thread" << endl; // 打印“Child Thread”,表示子线程在运行}return nullptr;
}// 主函数
int main()
{pthread_t tid; // 定义一个线程ID变量// 创建子线程,线程的执行函数是 mythread,其他参数为默认值pthread_create(&tid, nullptr, mythread, nullptr);// 主线程执行,循环 6 次for(int i = 0; i < 6; i++) {cout << "Main Thread" << endl; // 打印“Main Thread”,表示主线程在运行sleep(1); // 让主线程睡眠 1 秒钟}// 等待子线程完成执行pthread_join(tid, nullptr); // 阻塞等待 tid 线程执行完毕return 0; // 程序正常退出
}
pthread_create():用于创建一个新的线程,mythread是新线程的执行函数。该函数接受参数nullptr,表示没有传递任何参数给线程。pthread_join(tid, nullptr):等待线程tid执行完成,pthread_join阻塞主线程,直到子线程执行完毕。
打印结果:

线程退出
pthread_join

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
参数说明:
pthread_t thread:要等待结束的线程 ID。void **retval:用于存储线程返回值的指针。如果不需要返回值,可以将其设置为NULL。
功能描述:
pthread_join()函数使得调用该函数的线程(通常是主线程)阻塞,直到指定的线程(由thread参数指定)执行完毕。- 当线程结束后,系统会回收该线程的资源。如果
retval不为NULL,线程的返回值将被存储在retval指向的位置。
示例:
#include<iostream>
#include<unistd.h>
#include<pthread.h>using namespace std;// 子线程的执行函数
void* mythread(void*args)
{// 循环6次,每次打印一次信息,并且每次暂停1秒for(int i = 0 ; i < 6 ;i++) {sleep(1); // 休眠1秒钟cout << "Child Thread is running ...." << endl; // 输出子线程运行的提示}// 返回一个值100,并强制转换为 void* 类型return (void*)100;
}int main()
{pthread_t tid; // 定义一个线程ID变量// 创建子线程,传入 mythread 函数作为线程执行的函数,参数为 nullptrpthread_create(&tid, nullptr, mythread, nullptr);// 主线程运行6次,每次输出一次信息,并且每次暂停1秒for(int i = 0 ; i < 6 ;i++) {cout << "Main Thread is running .." << endl; // 输出主线程运行的提示sleep(1); // 休眠1秒钟}void* retval; // 声明一个指向 void 的指针,用于接收子线程的返回值// 等待子线程结束,并将子线程的返回值存储到 retval 中pthread_join(tid, &retval);// 输出子线程的返回值,将其强制转换为整数类型并输出cout << (int64_t)retval << endl; // 输出子线程的返回值,转换为整数return 0;
}
程序流程:
- 并发输出:主线程和子线程的输出交替进行,打印在终端上时会交替显示 “Main Thread is running …” 和 “Child Thread is running …”。
- 同步:主线程通过
pthread_join()等待子线程完成,并获取子线程的返回值。 - 线程返回值:子线程通过
return (void*)100返回一个void*类型的值,主线程通过pthread_join()捕获该返回值并输出。
打印:

pthread_exit

#include <pthread.h>
void pthread_exit(void *retval);
参数说明:
retval:这是一个指针,线程退出时可以返回的值。该值可以被其他线程通过pthread_join获取,用来传递线程的退出状态或其他信息。- 注意:
retval可以是任何类型的指针,通常是一个线程退出的状态信息。
示例:
#include<iostream>
#include<unistd.h>
#include<pthread.h>using namespace std;void* mythread(void* args) // 线程函数
{// 子线程执行的操作,循环输出 "Child Thread is running ...."for (int i = 0; i < 6; i++) {sleep(1); // 每次休眠1秒cout << "Child Thread is running ...." << endl;}// 输出结束前的信息cout << "Child Pthread_exit" << endl;// 使用 pthread_exit 显式退出线程并返回一个状态码 100pthread_exit((void*)100);// 这一行代码不会被执行到,因为 pthread_exit 已经退出线程return (void*)100;
}int main()
{pthread_t tid; // 声明一个线程标识符// 创建线程pthread_create(&tid, nullptr, mythread, nullptr);// 主线程执行的操作for (int i = 0; i < 6; i++) {cout << "Main Thread is running .." << endl;sleep(1); // 每次休眠1秒}// 主线程休眠,确保子线程有时间执行sleep(8); // 等待子线程执行完毕// 主线程结束前输出信息cout << "main return " << endl;return 0; // 程序结束
}
程序流程:
- 程序运行时,子线程和主线程会交替输出 “Child Thread is running …” 和 “Main Thread is running …”。
- 主线程执行完成它的循环后,调用
sleep(8)来等待子线程的执行。此时,子线程已经执行完它的循环,并通过pthread_exit显式退出。 - 由于没有在主线程中调用
pthread_join(),主线程并没有等待子线程完成,它只是通过sleep(8)暂停一段时间,确保子线程有足够的时间结束。
打印:

pthread_cancal

#include <pthread.h>
int pthread_cancel(pthread_t thread);
参数说明:
pthread_t thread:目标线程的线程 ID(TID),即你希望取消的线程。
功能描述:
pthread_cancel用于向指定的线程发送取消请求。调用此函数后,目标线程会接收到取消请求,并在合适的时机响应取消请求。
返回值:
- 返回 0 表示成功,其他返回值表示失败,通常是因为无法取消线程(如线程已经结束等)。
示例:
#include<iostream>
#include<unistd.h>
#include<pthread.h>
#include<vector>
#include<string>
using namespace std;// Hex函数,用于将整数转换为十六进制字符串
string Hex(int data)
{char buff[1034] = {0};// snprintf用于将整数data转换为十六进制字符串snprintf(buff,sizeof(buff),"0x%x",data);return buff;
}// 线程函数,打印每个线程的整数值
void* mythread(void* args)
{// 将传入的参数指针转换为整型指针int* i = (int*)args;while(true) // 无限循环,模拟线程的持续运行{sleep(1); // 每次休眠1秒cout << "thread: " << *i << endl; // 输出线程ID(即传递给线程的值)}return (void*)100; // 返回一个指针类型的值,通常线程退出时返回状态
}int main()
{// 定义一个 vector 来存储线程IDvector<pthread_t> th;// 创建4个线程for(int i = 0; i < 4; i++){pthread_t tid; // 定义一个线程ID变量// 创建线程,将线程ID、线程函数(mythread)以及传递给线程的参数(i的地址)传入pthread_create(&tid, nullptr, mythread, &i);// 将线程ID添加到线程容器中th.push_back(tid);}cout << "ready calcel" << endl;sleep(3); // 休眠3秒,等待线程输出// 取消每个创建的线程for(int i = 0; i < th.size(); i++){cout <<"calcel: thread " << i <<endl; // 输出正在取消的线程编号pthread_cancel(th[i]); // 取消对应的线程}sleep(1); // 稍等1秒,确保线程能够响应取消请求cout << "main return "<<endl; // 输出主线程返回信息return 0; // 程序结束
}
程序流程:
-
** 主线程创建子线程:**主线程使用
pthread_create()创建 4 个子线程,每个子线程打印传递给它的整数值(即循环中的i),并在每秒打印一次。 -
**线程输出:**每个线程在无限循环中每秒输出
thread: <value>,其中<value>是它收到的整数(传递给线程的i)。 -
**主线程等待:**主线程在创建完线程后,休眠 3 秒,允许线程输出信息。
-
**取消线程:**主线程在 3 秒后调用
pthread_cancel()来取消所有 4 个线程,每个线程被取消后,它会终止执行(线程的退出取决于它们在什么地方被取消)。 -
**程序结束:**主线程输出
"main return"并结束,程序执行完毕。
打印:

线程分离

#include <pthread.h>
int pthread_detach(pthread_t thread);
参数:
thread:要设置为分离状态的线程 ID。
功能描述
- 功能描述:
pthread_detach用于将一个线程设置为 分离状态。当线程被设置为分离状态后,线程在完成执行时会自动释放资源,而不需要其他线程显式地调用pthread_join()来清理线程资源。
示例:
#include<iostream>
#include<unistd.h>
#include<pthread.h>
#include<vector>
#include<string>
using namespace std;// Hex函数:将整数转换为十六进制字符串
string Hex(int data)
{char buff[1034] = {0};snprintf(buff, sizeof(buff), "0x%x", data); // 将整数以十六进制格式转换成字符串return buff;
}// 线程函数
void* mythread(void* args)
{pthread_detach(pthread_self()); // 将当前线程设置为分离状态int* i = (int*)args; // 将传入的参数指针转换为整型指针int n = 3;// 循环打印线程编号(传递给线程的值),共打印3次while(n--){sleep(1); // 每次循环休眠1秒cout << "thread: " << *i << endl; // 输出线程的编号}return (void*)100; // 线程返回值
}int main()
{vector<pthread_t> th; // 用于存储线程ID的容器cout << "线程的创建" << endl;// 创建4个线程for(int i = 0; i < 4; i++){pthread_t tid;pthread_create(&tid, nullptr, mythread, &i); // 创建线程并传递 i 的地址作为参数th.push_back(tid); // 将创建的线程ID加入到线程容器中}sleep(10); // 主线程休眠10秒,确保所有子线程能运行完cout << "main return " << endl;return 0; // 主程序结束
}
程序执行流程:
- 主线程通过
pthread_create创建 4 个子线程,每个子线程都会执行mythread函数。 - 每个子线程会打印 3 次其编号(线程的参数),并在每次打印后休眠 1 秒。
- 主线程休眠 10 秒,以确保子线程有足够的时间打印信息。
- 每个子线程在完成执行后会自动退出并释放资源,因为它们是分离线程(调用了
pthread_detach)。 - 主线程输出 “main return” 并结束。
打印:

- 以上的代码没有实现同步,可能会导致错乱打印。
相关文章:
深入理解Linux中的线程控制:多线程编程的实战技巧
个人主页:chian-ocean 文章专栏-Linux 前言: POSIX线程(Pthreads) 是一种在 POSIX 标准下定义的线程库,它为多线程编程提供了统一的接口,主要用于 UNIX 和类 UNIX 系统(如 Linux、MacOS 和 BS…...
【题解-Acwing】790. 数的三次方根
题目:790. 数的三次方根 题目描述 给定一个浮点数 n,求它的三次方根。 输入 共一行,包含一个浮点数 n 。 输出 共一行,包含一个浮点数,表示问题的解。 注意,结果保留 6 位小数。 数据范围 −10000 ≤ n ≤ 10000 时空限制 1s / 64MB 输入样例 1000.00输出样…...
【条形码识别改名工具】如何批量识别图片条形码,并以条码内容批量重命名,基于WPF和Zxing的开发总结
批量图片条形码识别与重命名系统 (WPF + ZXing)开发总结 项目适用场景 电商商品管理:批量处理商品图片,根据条形码自动分类归档图书馆系统:扫描图书条形码快速建立电子档案医疗档案管理:通过药品条形码整理医疗图片资料仓储管理:自动化识…...
大模型微服务架构模块实现方案,基于LLaMA Factory和Nebius Cloud实现模型精调的标准流程及代码
以下是基于LLaMA Factory和Nebius Cloud实现模型精调的标准流程及代码示例,结合最新技术动态和行业实践整理: 一、LLaMA Factory本地部署方案 1. 环境配置 # 创建Python环境并安装依赖 conda create -n llama_factory python3.10 conda activate llam…...
【C++】 —— 笔试刷题day_22
一、添加字符 题目解析 这道题,给定两个字符串A和B,字符串A的长度要小于B的长度; 现在我们要对A字符串添加字符,使得A字符串长度等于B字符串的长度,并且要求对应位置的字母尽量相等,然后求出来不相等的字符…...
深入浅出:LDAP 协议全面解析
在网络安全和系统管理的世界中,LDAP(轻量级目录访问协议,Lightweight Directory Access Protocol)是一个不可忽视的核心技术。它广泛应用于身份管理、认证授权以及目录服务,尤其在企业级环境中占据重要地位。本文将从基…...
【Android面试八股文】Android应用进程的启动流程【二】
应用进程 1.1 Android系统进程的启动过程: 1、init进程fork出Zygote进程后,Zygote进程会创建一个服务端socket,等待AMS发起socket请求。 同时,由Zygote进程fork出的SystemServer进程会启动各项系统服务,其中就包含了A…...
“星睿O6” AI PC开发套件评测 - 部署PVE搭建All in One NAS服务器
Radxa O6平台上部署PVE搭建All in One NAS服务器 Radxa O6是一款性能卓越的单板计算机,其强劲的硬件配置和多样化的接口设计,使其成为家庭和小型企业理想的All in One服务器解决方案。值得一提的是,O6原生配备了两个5G网口,便于直…...
16.使用豆包将docker-compose的yaml转为k8s的yaml,安装各种无状态服务
文章目录 docker方式httpbinit-toolslinux-commandmyipreference docker-compose安装k8s方式 docker方式 httpbin A simple HTTP Request & Response Service https://httpbin.org/ https://github.com/postmanlabs/httpbin https://github.com/mccutchen/go-httpbin do…...
全志H5,NanopiKP1lus移植QT5.12记录
移植步骤 机器环境下载QT5.12.0源码安装交叉编译器修改qmake.conf文件配置编译选项qt5的configure选项说明基本配置选项编译器和链接器选项功能模块配置第三方库集成注意事项 配置过程报错解决配置完成编译过程报错解决编译完成将arm-qt文件夹传送到开发板配置板子环境变量运行…...
定制一款国密浏览器(10):移植SM2算法前,解决错误码的定义问题
上一章中,我给大家介绍了 SM4 在 BoringSSL 上的移植要点,本来计划本章介绍 SM2 算法的移植要点。在移植 SM2 过程中,遇到了一个拦路虎,所以先扫除这个拦路虎,这就是错误码的定义问题。 在铜锁中,引入了几个错误码和错误字符串,在文件 sm2_err.c 中: static const ER…...
使用EXCEL绘制平滑曲线
播主播主,你都多少天没更新了!!!泥在干什么?你还做这个账号麻?!!! 做的做的(哭唧唧),就是最近有些忙,以及…… 前言&…...
Warcraft Logs [Classic] [WCL] Usage Wizard <HTOC>
HTOC(十字军的试炼)副本中各个BOSS的ID如下: 629 - 诺森德野兽 633 - 加拉克苏斯大王 637 - 派系冠军 641 - 瓦格里双子 645 - 阿努巴拉克 encounterID!637 and encounterID!641 encounterID NOT IN (637,641) 伤害 …...
【笔记】网络安全管理
计算机硬件中,运算器和控制器通常集成在一块芯片内,一般称为()。 数据库DB、数据库系统DBS、数据库管理系统DBMS,三者之间的关系是()。 OSI/RM体系结构中的网络层与TCP/IP体系结构中的&#x…...
在服务器上部署MinIO Server
MinIO的优势 高性能:MinIO号称是目前速度最快的对象存储服务器,据称在标准硬件上,对象存储的读/写速度最高可以高达183 GB/s和171 GB/s,可惜我的磁盘跟不上 兼容性:MinIO基于Amazon S3协议,并提供了与S3兼…...
一个改善Entity Framework异常处理和错误信息的开源项目
使用DDD从零构建一个完整的系统 使用Entity Framework作为ORM框架应该是绝大多数项目的选择,使得我们操作数据库变得简单方便;但是我们操作数据库,绝对是无法避免数据库发生异常的情况,数据库针对每一种异常也都会提供一个编码来…...
计算机视觉——基于 Yolov8 目标检测与 OpenCV 光流实现目标追踪
1. 概述 目标检测(Object Detection)和目标追踪(Object Tracking)是计算机视觉中的两个关键技术,它们在多种实际应用场景中发挥着重要作用。 目标检测指的是在静态图像或视频帧中识别出特定类别的目标对象࿰…...
PHP使用pandoc把markdown文件转为word
文章目录 首先安装pandocPHP处理 服务器操作系统是Linux,centos 首先安装pandoc yum install -y pandoc安装完成后输入如下代码,检查安装是否成功 pandoc --versionPHP处理 我把markdown内容存到了数据库里,所以要从数据库读取内容。对内容…...
第二十四天 - 分布式任务队列 - Celery高级应用 - 练习:分布式监控任务系统
一、Celery核心机制解析 1.1 分布式架构四要素 # celery_config.py BROKER_URL redis://:passwordlocalhost:6379/0 # 消息中间件 RESULT_BACKEND redis://:passwordlocalhost:6379/1 # 结果存储 TASK_SERIALIZER json ACCEPT_CONTENT [json] TIMEZONE Asia/Shanghai核…...
针对MCP认证考试中的常见技术难题进行实战分析与解决方案分享
一、身份与权限管理类难题 场景1:Active Directory组策略(GPO)不生效 问题现象:客户端计算机未应用新建的组策略。排查步骤: 检查GPO链接顺序:使用gpresult /r查看策略优先级,确保目标OU的GPO…...
【滑动窗口】最⼤连续 1 的个数 III(medium)
⼤连续 1 的个数 III(medium) 题⽬描述:解法(滑动窗⼝):算法思路:算法流程: C 算法代码:Java 算法代码: 题⽬链接:1004. 最⼤连续 1 的个数 III …...
OBS 日期时间.毫秒时间脚本 date-and-time.lua
文章目录 OBS 日期时间.毫秒时间脚本:效果 OBS 日期时间.毫秒时间脚本: obs obslua source_name ""last_text "" format_string "" activated false-- 此函数用于获取精确的毫秒级时间戳&#…...
探索大语言模型(LLM):目标、原理、挑战与解决方案
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言语言模型的目标语言模型的数学表示语言模型面临的挑战解决参数量巨大的方法1. 马尔可夫假设2. 神经网络语言模型3.自监督学习4. 分布式表示 脑图总结 前言 在自…...
ES基本操作(Java API)
1. 导入restClient依赖 <!-- es --><dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-client</artifactId><version>7.12.1</version></dependency> <!…...
得物官网sign签名逆向分析
打开得物官网,点击鞋类,可以看到请求 直接搜sign function p(e) {return f()("".concat(e ? s()(e).sort().reduce(function(t, n) {return "".concat(t).concat(n).concat(e[n])}, "") : "", "048a9…...
Agent的九种设计模式 介绍
Agent的九种设计模式 介绍 一、ReAct模式 原理:将推理(Reasoning)和行动(Acting)相结合,使Agent能够在推理的指导下采取行动,并根据行动的结果进一步推理,形成一个循环。Agent通过生成一系列的思维链(Thought Chains)来明确推理步骤,并根据推理结果执行相应的动作,…...
vivado 时钟IP核(MMCM PLL)
CMT简介 FPGA中时钟管理模块(CMT)包括PLL和MMCM,用于将时钟倍频(比如输入时钟25M,我们要产生50M时钟)、分频(在不影响系统功能的前提下,较低的工作时钟,能够降低系统功耗)、改变相位偏移或占空比等。 当需要…...
hackmyvm-airbind
收集信息 arp-scan -l nmap -sS -v 192.168.195.162 访问扫描到的ip,直接跳转到登录页面,利用admin/admin弱口令登录 在settings.php中找到一处文件上传,上传一句话木马,上传成功 反弹shell 上传php-reverse-shell.php 抓包&am…...
知识了解03——怎么解决使用npm包下载慢的问题?
1、为什么使用npm下载包会下载的慢 因为使用npm下载包时,默认使用国外服务器进行下载,此时的网络传输需要经过漫长的海底电缆,因此下载速度会变慢 2、怎么解决?(切换镜像源) (1)方…...
[晕事]今天做了件晕事71,_GNU_SOURCE
今天碰到一件晕,从别的地方搬运来一段代码,里面有使用in6_pktinfo这个结构体: struct in6_pktinfo pktinfo; 通过搜索发现需要include的头文件就是:netinet/in.h。加上这个头文件,还是出现找不到结构体的错误。最后通过仔细查看头文件,发现,这个结构体定义是在宏判断里…...
