[OS] pthreads-1
线程的基本概念
线程是进程中的一个单一的执行流。一个进程可以包含多个线程,这些线程共享进程中的资源,并且在相同的地址空间中执行。多线程是提高应用程序并行性的流行方法。例如,在浏览器中,不同的标签页可以视作独立的线程。
通俗解释
简单来说,一个程序(也就是进程)可以有多个小的执行单元,这些小的执行单元叫做线程。就像是一个工厂的流水线,每条流水线都能并行地完成工作,这样整个工厂(程序)就能更快地处理任务。
在现代应用中,多线程是非常有用的。例如在浏览器中,每一个标签页可能是一个独立的线程,这样如果一个标签页卡住了,其他标签页还能继续工作,而不会全部停顿。
示例代码:使用 Pthread 实现多线程
在 C 语言中,我们可以使用 POSIX 线程(Pthread)库来实现Linux系统下的多线程编程。POSIX 线程库是基于标准的 C/C++ 线程 API,包含在 <pthread.h> 头文件中。下面我们来写一个简单的多线程程序。
#include <pthread.h> // 包含 Pthread 库
#include <stdio.h> // 标准输入输出库
#include <stdlib.h> // 标准库// 线程要执行的函数
void* myThreadFunction(void* arg) {int threadID = *((int*)arg);printf("Hello from thread %d\n", threadID);pthread_exit(NULL); // 线程结束
}int main() {pthread_t threads[3]; // 创建一个线程数组,包含3个线程int threadArgs[3]; // 每个线程的参数int rc; // 返回代码for (int i = 0; i < 3; i++) {threadArgs[i] = i + 1; // 给每个线程分配一个IDprintf("Creating thread %d\n", i + 1);rc = pthread_create(&threads[i], NULL, myThreadFunction, (void*)&threadArgs[i]);if (rc) {printf("Error: unable to create thread %d\n", rc);exit(-1);}}// 等待所有线程完成for (int i = 0; i < 3; i++) {pthread_join(threads[i], NULL);}printf("All threads completed.\n");return 0;
}
解释代码中的每个部分:
-
头文件包含:
#include <pthread.h>:包含 POSIX 线程库的头文件,提供多线程的相关函数。#include <stdio.h>和#include <stdlib.h>用于输入输出和内存管理。
-
线程函数:
void* myThreadFunction(void* arg):这是每个线程的执行函数。它接受一个参数(这里是线程的 ID),并打印出线程的问候信息。pthread_exit(NULL):表示线程正常结束。
-
主函数:
pthread_t threads[3]:声明一个包含 3 个线程的数组。threadArgs[i] = i + 1:给每个线程分配一个唯一的 ID。pthread_create(...):创建线程,并启动myThreadFunction函数执行。pthread_join(threads[i], NULL):等待所有线程完成,主程序会在所有线程执行完毕之后再继续。
如何编译和运行代码:
-
编译命令:
-
示例输出:
Creating thread 1
Creating thread 2
Creating thread 3
Hello from thread 1
Hello from thread 2
Hello from thread 3
All threads completed.
2. Pthread 的创建和终止
2.1 Pthread 的声明
在使用 Pthread 时,我们需要首先声明线程变量。Pthread 使用 pthread_t 类型来声明线程。
示例:
pthread_t threads[NUM_THREAD];
pthread_t 是一个结构类型,用于标识一个线程。在上面的代码中,threads 是一个包含 NUM_THREAD 个线程的数组。
2.2 Pthread 的创建
要创建一个新线程,我们可以使用 pthread_create() 函数:
int pthread_create(pthread_t *thread, // 线程指针,用于接收新创建的线程标识const pthread_attr_t *attr, // 线程属性参数,可以用于设置调度策略等void *(*start_routine) (void *), // 线程函数,线程开始执行的地方void *arg // 传递给线程函数的参数
);
-
参数解释:
thread:指向pthread_t对象的指针,用于存储新创建的线程标识。attr:用于设置线程属性的参数,可以设置线程的调度策略、分离状态等。通常为NULL表示使用默认属性。start_routine:线程创建后将执行的函数,函数的类型为void* function(void*)。arg:指向传递给线程函数的参数的指针,可以为NULL。
-
返回值:
- 成功时,
pthread_create()返回0。 - 如果失败,则返回错误号,且线程标识不确定。
- 成功时,
示例代码:创建线程
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>#define NUM_THREADS 3void* threadFunction(void* arg) {int threadID = *((int*)arg);printf("Hello from thread %d\n", threadID);pthread_exit(NULL); // 线程完成工作后调用 pthread_exit 显式退出
}int main() {pthread_t threads[NUM_THREADS]; // 声明线程数组int threadArgs[NUM_THREADS]; // 每个线程的参数int rc; // 返回代码for (int i = 0; i < NUM_THREADS; i++) {threadArgs[i] = i + 1; // 给每个线程分配一个 IDprintf("Creating thread %d\n", i + 1);rc = pthread_create(&threads[i], NULL, threadFunction, (void*)&threadArgs[i]);if (rc) {printf("Error: unable to create thread %d\n", rc);exit(-1);}}// 等待所有线程完成for (int i = 0; i < NUM_THREADS; i++) {pthread_join(threads[i], NULL);}printf("All threads completed.\n");return 0;
}
在上面的代码中:
- 使用
pthread_create()创建线程,并指定每个线程要执行的函数threadFunction。 - 通过
pthread_join()来等待每个线程的完成。
2.3 Pthread 的终止
线程在完成其任务时可以显式地调用 pthread_exit() 函数来终止:
void pthread_exit(void *value_ptr);
- 参数解释:
value_ptr:指向一个整型变量的指针,用于存储线程的返回状态。这个变量应该是全局的,这样等待该线程的其他线程就可以读取它的返回状态。
线程退出时,通常会调用 pthread_exit(),确保其他线程能够正确地获知该线程的退出状态。
在前面的示例代码中,我们使用了 pthread_exit(NULL),表示线程成功完成其任务且没有特定的返回值。
通俗解释
pthread_create() 就像给每个工人发放工作指令,告诉他们该做什么,以及如何做;而 pthread_exit() 就是让工人在完成任务后报告说“我已经完成了”。
在代码中,pthread_create() 创建了线程,并传递了一个函数 threadFunction 给它,这个函数是线程创建后将要执行的内容。当线程完成其工作后,调用 pthread_exit() 来退出。
2.4 示例 1:使用 pthread_exit() 函数
在这个示例中,我们创建了 5 个线程,每个线程执行一个简单的打印任务,输出“Hello World!”并带有线程的 ID。以下是完整代码:
#include <pthread.h> // 包含 Pthread 库
#include <stdio.h> // 标准输入输出库
#include <stdlib.h> // 标准库#define NUM_THREADS 5 // 定义线程数// 线程函数
void *PrintHello(void *threadid) {int tid = (int)threadid; // 将传入的参数转换为整数类型printf("\n%d: Hello World!\n", tid);pthread_exit(NULL); // 线程完成工作后退出
}int main(int argc, char *argv[]) {pthread_t threads[NUM_THREADS]; // 声明一个包含 NUM_THREADS 个线程的数组int rc; // 返回代码int t;// 创建线程for (t = 0; t < NUM_THREADS; t++) {printf("Creating thread %d\n", t);rc = pthread_create(&threads[t], NULL, PrintHello, (void *)t);if (rc) {printf("ERROR; return code from pthread_create() is %d\n", rc);exit(-1);}}pthread_exit(NULL); // 主线程调用 pthread_exit 以确保子线程能够执行完毕
}
代码详细解释
-
头文件包含:
#include <pthread.h>:包含 POSIX 线程库的头文件,提供多线程相关函数。#include <stdio.h>和#include <stdlib.h>用于输入输出和内存管理。
-
宏定义:
#define NUM_THREADS 5:定义线程数量为 5。
-
线程函数
PrintHello:void *PrintHello(void *threadid):每个线程执行的函数。- 函数参数
threadid是线程的 ID,将它转换为int类型以便输出。 - 使用
pthread_exit(NULL)来退出线程,确保线程正常结束。
-
主函数
main:- 声明
pthread_t threads[NUM_THREADS]:包含 5 个线程的数组。 - 使用
pthread_create()创建线程,循环NUM_THREADS次,每次创建一个线程并传递线程 ID。 - 如果
pthread_create()失败,打印错误代码并退出程序。 - 主线程调用
pthread_exit(NULL)以防止主程序退出导致子线程未完成就被强制终止。
- 声明
输出结果示例
Creating thread 0
Creating thread 1
Creating thread 2
Creating thread 3
Creating thread 44: Hello World!
3: Hello World!
0: Hello World!
1: Hello World!
2: Hello World!
解释输出
在这个例子中,主线程首先创建 5 个子线程,然后每个线程执行 PrintHello 函数来打印 "Hello World!" 和线程的 ID。
需要注意的是,多线程的执行顺序并不确定。因此,输出的顺序可能会有所不同,例如线程 4 可能先输出,而线程 0 可能在最后输出。这是因为每个线程是独立并行执行的,具体的执行顺序取决于系统的线程调度。
通俗解释
这个程序的目的是演示如何使用 Pthread 来创建和管理多个线程。在代码中,我们创建了 5 个独立的线程,每个线程都执行相同的函数 PrintHello,只是输出的内容不同——它们会显示各自的线程 ID。
在输出结果中,我们看到每个线程的输出顺序是随机的,这是因为多线程是并行执行的,系统会根据资源的可用性来调度每个线程何时运行。这种随机性是多线程编程的特点,也是它的优势所在——可以同时做多个任务,从而提高效率。
pthread_exit() 的作用
pthread_exit(NULL) 用于让线程正常结束。它在主线程中调用,确保所有的子线程在主线程结束之前都能够顺利完成任务。否则,如果主线程执行完毕,整个程序可能会直接退出,而导致子线程的执行被强制中断。
总结
- 使用
pthread_create()来创建线程,并传递函数指针作为线程的任务。 pthread_exit()用于显式退出线程,确保它们有机会完成其任务。- 多线程的输出顺序可能是随机的,因为线程是并行执行的。
2.5 示例 2:pthread_exit() 的影响
在多线程程序中,如果主线程(即 main() 函数)在子线程完成之前退出,子线程会自动终止。因此,推荐在 main() 中使用 pthread_exit(),以确保所有线程都有机会完成其任务。
#include <pthread.h> // 包含 Pthread 库
#include <stdio.h> // 标准输入输出库
#include <stdlib.h> // 标准库
#include <unistd.h> // 包含 sleep() 函数// 线程函数
void *PrintHello(void *threadid) {sleep(2); // 线程休眠2秒printf("Hello World!\n");pthread_exit(NULL); // 线程正常退出
}int main(int argc, char *argv[]) {pthread_t thread; // 声明一个线程int rc; // 返回代码void *i;printf("In main: create thread\n");rc = pthread_create(&thread, NULL, PrintHello, i);if (rc) {printf("ERROR; return code from pthread_create() is %d\n", rc);exit(1);}printf("Main thread exits!\n");// 如果取消注释 pthread_exit(NULL),主线程会等待所有线程完成后再退出// pthread_exit(NULL);return 0; // 主线程直接退出
}
代码解释
-
头文件包含:
#include <pthread.h>:包含 Pthread 库的头文件,用于多线程操作。#include <stdio.h>和#include <stdlib.h>:标准输入输出和内存管理库。#include <unistd.h>:包含sleep()函数,用于让线程休眠指定时间。
-
线程函数
PrintHello:void *PrintHello(void *threadid):线程的任务是休眠 2 秒后打印 "Hello World!"。sleep(2):让线程休眠 2 秒。pthread_exit(NULL):线程完成任务后调用pthread_exit()进行正常退出。
-
主函数
main:- 声明了一个线程
pthread_t thread。 - 使用
pthread_create()创建线程,线程将执行PrintHello函数。 pthread_exit(NULL)被注释掉了。如果取消注释,则主线程会等待所有子线程完成工作后再退出。
- 声明了一个线程
输出结果
-
如果
pthread_exit(NULL)被注释掉(如代码所示):In main: create thread Main thread exits! -
主线程在子线程还没有完成任务时就退出了,因此子线程会被强制终止,导致 "Hello World!" 没有被输出。
-
如果
pthread_exit(NULL)没有被注释掉In main: create thread Main thread exits! Hello World!主线程调用了
pthread_exit(),使得主线程会等待子线程完成所有任务后再退出。这样 "Hello World!" 会正常输出。
通俗解释
在这个例子中,我们创建了一个子线程,子线程的任务是等待 2 秒后打印 "Hello World!"。但是,如果主线程(main())在子线程完成之前退出,那么所有的子线程都会被强制终止。因此,我们建议在 main() 中使用 pthread_exit(),这样主线程不会立即结束,而是等待所有的子线程完成工作。
可以把主线程想象成一场演出中的主持人,而子线程是表演者。如果主持人(主线程)在表演还没结束的时候就离开了,那么表演者(子线程)就不得不中断演出。使用 pthread_exit() 可以确保主持人等到所有表演者完成他们的演出后再离场。
pthread_exit() 的作用
- 当
pthread_exit()被调用时,主线程不会立即退出,它会等待所有的子线程完成。 - 如果主线程直接调用
return,程序会结束,所有未完成的线程会被强制终止,导致一些任务可能无法完成。
总结
pthread_create():用于创建线程,主线程可以用它来启动子线程。pthread_exit():用于确保主线程等待所有子线程完成任务后再退出。- 主线程的退出方式会影响子线程:如果主线程直接退出,子线程将被强制终止;使用
pthread_exit()则可避免这个问题
相关文章:
[OS] pthreads-1
线程的基本概念 线程是进程中的一个单一的执行流。一个进程可以包含多个线程,这些线程共享进程中的资源,并且在相同的地址空间中执行。多线程是提高应用程序并行性的流行方法。例如,在浏览器中,不同的标签页可以视作独立的线程。…...
ThreeJS入门(137):THREE.StringKeyframeTrack 知识详解,示例代码
作者: 还是大剑师兰特 ,曾为美国某知名大学计算机专业研究生,现为国内GIS领域高级前端工程师,CSDN知名博主,深耕openlayers、leaflet、mapbox、cesium,webgl,ThreeJS,canvas…...
用大模型或者向量模型比如huggingface上的模型,处理一批图片,对该图片进行分类,检索
要使用大模型或向量模型对图片进行分类和检索,通常可以采用以下几种方法: 1. **图像分类**:使用预训练的图像分类模型(如ResNet、EfficientNet等)对图片进行分类。 2. **图像特征提取**:使用预训练的模型(如CLIP、ResNet等)提取图像的特征向量,然后进行相似度检索。 …...
Mac 使用 zsh 终端提示 zsh: killed 的问题
我的脚本的内容为: #!/bin/bashset -epids$(ps -ef | grep consul | grep -v grep | awk {print $2})for pid in $pids; doecho "kill process: $pid"kill -9 $pid donecd $(dirname $0)nohup ./consul agent -dev > nohup.log &可以看到这是一个…...
数字后端零基础入门系列 | Innovus零基础LAB学习Day6
今天没有具体的数字IC后端lab实验。今天的重点是熟悉掌握静态时序分析STA中的几类timing path以及setup和hold检查机制(包含setup和hold计算公式)。 芯片流片失败的那些故事 数字后端零基础入门系列 | Innovus零基础LAB学习Day5 等大家把今天内容学习…...
(Linux驱动学习 -13).SPI驱动实验
目录 一.SPI驱动相关结构体与函数 1.struct spi_master 结构体 2.申请 spi_master - spi_alloc_master 3.释放 spi_master - spi_master_put 4.向内核注册 spi_master - spi_register_master 5.注销掉 spi_master 6.struct spi_driver 结构体 7.向内核注册 spi_driver -…...
Angular 框架入门教程:从安装到路由、服务与状态管理详解
一、引言 在前端开发领域,Angular 是一个强大且流行的框架。它由 Google 维护,基于 TypeScript,采用模块化设计,提供了组件化开发、依赖注入、路由、表单处理等丰富功能,旨在帮助开发者构建高效、可维护的单页应用程序…...
【华为HCIP实战课程十八】OSPF的外部路由类型,网络工程师
一、外部路由类型: 上节讲的外部路由类型,无关乎COST大小,OSPF外部路由类型1优先于外部路由类型2 二、转发地址实验拓扑 我们再SW3/R5/R6三台设备运行RIP,SW3即运行RIP又运行OSPF SW3配置rip [SW3-rip-1]ver 2 [SW3-rip-1]network 10.0.0.0 AR5去掉ospf配置和AR6配置rip…...
oss 简单命令(已亲测)
1、 服务器本地文件复制到OSS指定目录 ./ossutil cp -r /opt/post/afc/afcServer/afcenter/logs/ oss://oss-name/ScBak/20230608/ -c /opt/post/ossconfig 2、在oss服务器上创建文件夹 ./ossutil mkdir oss://oss-name/ScBak/20230608/dam -c /opt/post/ossconfig 3、查…...
申请https证书
引入证书: 当服务器使用HTTPS之前都会申请一份证书,证书是为了证明服务端公钥的权威性,服务器向浏览器传输证书,浏览器再从证书里获取公钥,证书明文数据签名。 如何理解CA签发证书的过程 a.CA会有自己的公钥 和 私钥ÿ…...
trtexec 工具使用
本文介绍trtexec工具的使用,trtexec可以实现onnx模型导出trt模型、耗时分析和模型优化分析等功能,本节将对 trtexec的运用进行介绍。 1.trtexec trtexec是官方提供的命令行工具,主要用于一下三个方面 生成模型序列化文件:由ONNX文…...
10款具备强大数据报告功能的电脑监控工具,办公电脑怎么监控
数据报告功能是电脑监控软件的重要特性,它能够帮助管理者全面了解员工的工作行为、应用使用情况,并生成详细的生产力分析报告。以下是10款具备强大数据报告功能的监控工具推荐,帮助企业有效管理和提升工作效率。 1. 固信软件 固信软件不仅是…...
如何理解Linux中的进程名
目录 一:程序的概念二:进程的概念三:线程的概念四:Linux中的进程名 一:程序的概念 程序就是采用某种特定格式编写的文本文件,该文件可以给编译器或者解释器编译和解释。编写好的程序平时存放在硬盘中。 二…...
微信红包设计流程讲解与实战分析
#1024程序员节 | 征文# 前言 微信红包作为大家耳熟能详的一种互动方式,其背后的技术支持包含多个方面。从用户发出红包到红包被抢完,涉及到的流程包括发红包、红包存储、红包拆分以及抢红包等。本文将详细介绍这一系列流程,并通过代码案例来…...
AI智能体:AI智能体(Agent)是什么?为什么要学?99%的人不知道!
为什么要学? 我们先搞清楚为什么? 最近看到 AI 创新力五问,我们日常生活中有使用 AI 来融入到我们的学习工作流嘛? 值得我们日常反省。 未来企业人才招聘测试AI创新力的五问: 您是否处于每天习惯使用 AI 的状态&am…...
NVR小程序接入平台/设备EasyNVR多个NVR同时管理的高效解决方案
在当今的数字化安防时代,视频监控系统的需求日益复杂和多样化。为了满足不同场景下的监控需求,一种高效、灵活且兼容性强的安防视频监控平台——NVR批量管理软件/平台EasyNVR应运而生。本篇探讨这一融合所带来的创新与发展。 一、NVR监测软件/设备EasyNV…...
APS开源源码解读: 排程工具 optaplanner II
上篇 排产,原则上也就是分配时间,分配资源;保证资源日历约束,保证工艺路线约束。我们看一下如何实现optaplanner 优化的 定义一个move, 一个move可能改变了分配到的资源,也可能改变了一个资源上的顺序。改变即意味着优…...
科技是把双刃剑,巧用技术改变财务预测
数字化和全球化的双向驱动,引领我国各行各业在技术革新的浪潮中不断扬帆。这一趋势不仅带来了前所未有的突破与创新,推进企业迈向数据驱动决策的新未来,同时也伴随着一些潜在的问题和挑战。科技的普及就像是一场革命,在财务管理领…...
vscode默认添加python项目的源目录路径到执行环境(解决ModuleNotFoundError: No module named问题)
0. 问题描述 vscode中编写python脚本,导入工程目录下的其他模块,出现ModuleNotFoundError: No module named 错误 在test2的ccc.py文件中执行print(sys.path) 查看路径 返回结果发现并无’/home/xxx/first_demo’的路径,所以test2下面的文…...
【每日刷题】Day143
【每日刷题】Day143 🥕个人主页:开敲🍉 🔥所属专栏:每日刷题🍍 🌼文章目录🌼 1. 200. 岛屿数量 - 力扣(LeetCode) 2. LCR 105. 岛屿的最大面积 - 力扣&…...
多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
Ascend NPU上适配Step-Audio模型
1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统,支持多语言对话(如 中文,英文,日语),语音情感(如 开心,悲伤)&#x…...
c#开发AI模型对话
AI模型 前面已经介绍了一般AI模型本地部署,直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型,但是目前国内可能使用不多,至少实践例子很少看见。开发训练模型就不介绍了&am…...
优选算法第十二讲:队列 + 宽搜 优先级队列
优选算法第十二讲:队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...
论文阅读笔记——Muffin: Testing Deep Learning Libraries via Neural Architecture Fuzzing
Muffin 论文 现有方法 CRADLE 和 LEMON,依赖模型推理阶段输出进行差分测试,但在训练阶段是不可行的,因为训练阶段直到最后才有固定输出,中间过程是不断变化的。API 库覆盖低,因为各个 API 都是在各种具体场景下使用。…...
【Elasticsearch】Elasticsearch 在大数据生态圈的地位 实践经验
Elasticsearch 在大数据生态圈的地位 & 实践经验 1.Elasticsearch 的优势1.1 Elasticsearch 解决的核心问题1.1.1 传统方案的短板1.1.2 Elasticsearch 的解决方案 1.2 与大数据组件的对比优势1.3 关键优势技术支撑1.4 Elasticsearch 的竞品1.4.1 全文搜索领域1.4.2 日志分析…...
tauri项目,如何在rust端读取电脑环境变量
如果想在前端通过调用来获取环境变量的值,可以通过标准的依赖: std::env::var(name).ok() 想在前端通过调用来获取,可以写一个command函数: #[tauri::command] pub fn get_env_var(name: String) -> Result<String, Stri…...
uni-app学习笔记三十五--扩展组件的安装和使用
由于内置组件不能满足日常开发需要,uniapp官方也提供了众多的扩展组件供我们使用。由于不是内置组件,需要安装才能使用。 一、安装扩展插件 安装方法: 1.访问uniapp官方文档组件部分:组件使用的入门教程 | uni-app官网 点击左侧…...
【实施指南】Android客户端HTTPS双向认证实施指南
🔐 一、所需准备材料 证书文件(6类核心文件) 类型 格式 作用 Android端要求 CA根证书 .crt/.pem 验证服务器/客户端证书合法性 需预置到Android信任库 服务器证书 .crt 服务器身份证明 客户端需持有以验证服务器 客户端证书 .crt 客户端身份…...
