深入理解Linux中的线程控制:多线程编程的实战技巧

news2025/4/21 6:27:40

个人主页: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);

参数解析:

  1. pthread_t *thread:指向 pthread_t 类型的变量,这个变量将存储新线程的 ID。
  2. const pthread_attr_t *attr:指向 pthread_attr_t 类型的指针,它包含了新线程的属性(如栈大小、调度策略等)。如果传入 NULL,则使用默认属性。
  3. void *(*start_routine)(void *):这是一个指向线程执行函数的指针,该函数接收一个 void* 类型的参数,并返回 void* 类型的结果。
  4. 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 函数作为线程执行的函数,参数为 nullptr
    pthread_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;
}

程序流程:
  1. 并发输出:主线程和子线程的输出交替进行,打印在终端上时会交替显示 “Main Thread is running …” 和 “Child Thread is running …”。
  2. 同步:主线程通过 pthread_join() 等待子线程完成,并获取子线程的返回值。
  3. 线程返回值:子线程通过 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 显式退出线程并返回一个状态码 100
    pthread_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 来存储线程ID
    vector<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;  // 主程序结束
}

程序执行流程:

  1. 主线程通过 pthread_create 创建 4 个子线程,每个子线程都会执行 mythread 函数。
  2. 每个子线程会打印 3 次其编号(线程的参数),并在每次打印后休眠 1 秒。
  3. 主线程休眠 10 秒,以确保子线程有足够的时间打印信息。
  4. 每个子线程在完成执行后会自动退出并释放资源,因为它们是分离线程(调用了 pthread_detach)。
  5. 主线程输出 “main return” 并结束。

打印:

在这里插入图片描述

  • 以上的代码没有实现同步,可能会导致错乱打印。

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

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

相关文章

【条形码识别改名工具】如何批量识别图片条形码,并以条码内容批量重命名,基于WPF和Zxing的开发总结

批量图片条形码识别与重命名系统 (WPF + ZXing)开发总结 项目适用场景 ​​电商商品管理​​:批量处理商品图片,根据条形码自动分类归档​​图书馆系统​​:扫描图书条形码快速建立电子档案​​医疗档案管理​​:通过药品条形码整理医疗图片资料​​仓储管理​​:自动化识…

【C++】 —— 笔试刷题day_22

一、添加字符 题目解析 这道题&#xff0c;给定两个字符串A和B&#xff0c;字符串A的长度要小于B的长度&#xff1b; 现在我们要对A字符串添加字符&#xff0c;使得A字符串长度等于B字符串的长度&#xff0c;并且要求对应位置的字母尽量相等&#xff0c;然后求出来不相等的字符…

【Android面试八股文】Android应用进程的启动流程【二】

应用进程 1.1 Android系统进程的启动过程&#xff1a; 1、init进程fork出Zygote进程后&#xff0c;Zygote进程会创建一个服务端socket&#xff0c;等待AMS发起socket请求。 同时&#xff0c;由Zygote进程fork出的SystemServer进程会启动各项系统服务&#xff0c;其中就包含了A…

“星睿O6” AI PC开发套件评测 - 部署PVE搭建All in One NAS服务器

Radxa O6平台上部署PVE搭建All in One NAS服务器 Radxa O6是一款性能卓越的单板计算机&#xff0c;其强劲的硬件配置和多样化的接口设计&#xff0c;使其成为家庭和小型企业理想的All in One服务器解决方案。值得一提的是&#xff0c;O6原生配备了两个5G网口&#xff0c;便于直…

全志H5,NanopiKP1lus移植QT5.12记录

移植步骤 机器环境下载QT5.12.0源码安装交叉编译器修改qmake.conf文件配置编译选项qt5的configure选项说明基本配置选项编译器和链接器选项功能模块配置第三方库集成注意事项 配置过程报错解决配置完成编译过程报错解决编译完成将arm-qt文件夹传送到开发板配置板子环境变量运行…

使用EXCEL绘制平滑曲线

播主播主&#xff0c;你都多少天没更新了&#xff01;&#xff01;&#xff01;泥在干什么&#xff1f;你还做这个账号麻&#xff1f;&#xff01;&#xff01;&#xff01; 做的做的&#xff08;哭唧唧&#xff09;&#xff0c;就是最近有些忙&#xff0c;以及…… 前言&…

Warcraft Logs [Classic] [WCL] Usage Wizard <HTOC>

‌HTOC&#xff08;十字军的试炼&#xff09;副本中各个BOSS的ID如下‌&#xff1a; ‌629 - 诺森德野兽‌ ‌633 - 加拉克苏斯大王‌ ‌637 - 派系冠军‌ ‌641 - 瓦格里双子‌ ‌645 - 阿努巴拉克‌ encounterID!637 and encounterID!641 encounterID NOT IN (637,641) 伤害 …

在服务器上部署MinIO Server

MinIO的优势 高性能&#xff1a;MinIO号称是目前速度最快的对象存储服务器&#xff0c;据称在标准硬件上&#xff0c;对象存储的读/写速度最高可以高达183 GB/s和171 GB/s&#xff0c;可惜我的磁盘跟不上 兼容性&#xff1a;MinIO基于Amazon S3协议&#xff0c;并提供了与S3兼…

一个改善Entity Framework异常处理和错误信息的开源项目

使用DDD从零构建一个完整的系统 使用Entity Framework作为ORM框架应该是绝大多数项目的选择&#xff0c;使得我们操作数据库变得简单方便&#xff1b;但是我们操作数据库&#xff0c;绝对是无法避免数据库发生异常的情况&#xff0c;数据库针对每一种异常也都会提供一个编码来…

计算机视觉——基于 Yolov8 目标检测与 OpenCV 光流实现目标追踪

1. 概述 目标检测&#xff08;Object Detection&#xff09;和目标追踪&#xff08;Object Tracking&#xff09;是计算机视觉中的两个关键技术&#xff0c;它们在多种实际应用场景中发挥着重要作用。 目标检测指的是在静态图像或视频帧中识别出特定类别的目标对象&#xff0…

PHP使用pandoc把markdown文件转为word

文章目录 首先安装pandocPHP处理 服务器操作系统是Linux&#xff0c;centos 首先安装pandoc yum install -y pandoc安装完成后输入如下代码&#xff0c;检查安装是否成功 pandoc --versionPHP处理 我把markdown内容存到了数据库里&#xff0c;所以要从数据库读取内容。对内容…

OBS 日期时间.毫秒时间脚本 date-and-time.lua

文章目录 OBS 日期时间.毫秒时间脚本&#xff1a;效果 OBS 日期时间.毫秒时间脚本&#xff1a; obs obslua source_name ""last_text "" format_string "" activated false-- 此函数用于获取精确的毫秒级时间戳&#…

探索大语言模型(LLM):目标、原理、挑战与解决方案

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言语言模型的目标语言模型的数学表示语言模型面临的挑战解决参数量巨大的方法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签名逆向分析

打开得物官网&#xff0c;点击鞋类&#xff0c;可以看到请求 直接搜sign function p(e) {return f()("".concat(e ? s()(e).sort().reduce(function(t, n) {return "".concat(t).concat(n).concat(e[n])}, "") : "", "048a9…

vivado 时钟IP核(MMCM PLL)

CMT简介 FPGA中时钟管理模块&#xff08;CMT&#xff09;包括PLL和MMCM&#xff0c;用于将时钟倍频(比如输入时钟25M&#xff0c;我们要产生50M时钟)、分频(在不影响系统功能的前提下&#xff0c;较低的工作时钟&#xff0c;能够降低系统功耗)、改变相位偏移或占空比等。 当需要…

hackmyvm-airbind

收集信息 arp-scan -l nmap -sS -v 192.168.195.162 访问扫描到的ip&#xff0c;直接跳转到登录页面&#xff0c;利用admin/admin弱口令登录 在settings.php中找到一处文件上传&#xff0c;上传一句话木马&#xff0c;上传成功 反弹shell 上传php-reverse-shell.php 抓包&am…

知识了解03——怎么解决使用npm包下载慢的问题?

1、为什么使用npm下载包会下载的慢 因为使用npm下载包时&#xff0c;默认使用国外服务器进行下载&#xff0c;此时的网络传输需要经过漫长的海底电缆&#xff0c;因此下载速度会变慢 2、怎么解决&#xff1f;&#xff08;切换镜像源&#xff09; &#xff08;1&#xff09;方…

【算法数据结构】leetcode37 解数独

37. 解数独 - 力扣&#xff08;LeetCode&#xff09; 题目描述&#xff1a; 题目要求每一行 &#xff0c;每一列&#xff0c;每个3*3 的子框只能出现一次。每个格子的数字范围1-9. 需要遍历每个空格填入可能的数字&#xff0c;并验证符合规则。如果符合就填入&#xff0c;不符…

招商信诺原点安全:一体化数据安全管理解决方案荣获“鑫智奖”!

近日&#xff0c;“鑫智奖 2025第七届金融数据智能优秀解决方案评选”榜单发布&#xff0c;原点安全申报的《招商信诺&#xff1a;数据安全一体化管理解决方案》荣获「信息安全创新优秀解决方案」。 “鑫智奖第七届金融数据智能优秀解决方案评选”活动由金科创新社主办&#x…