Linux线程(二)线程ID及创建线程详解

news2024/10/2 20:50:30

1.线程ID

就像每个进程都有一个进程 ID 一样,每个线程也有其对应的标识,称为线程 ID。进程 ID 在整个系统中是唯一的,但线程 ID 不同,线程 ID 只有在它所属的进程上下文中才有意义。

进程 ID 使用 pid_t 数据类型来表示,它是一个非负整数。而线程 ID 使用 pthread_t 数据类型来表示, 一个线程可通过库函数 pthread_self()来获取自己的线程 ID,其函数原型如下所示:

#include <pthread.h>

pthread_t pthread_self(void);

使用该函数需要包含头文件<pthread.h>。

该函数调用总是成功,返回当前线程的线程 ID。

可以使用 pthread_equal()函数来检查两个线程 ID 是否相等,其函数原型如下所示:

#include <pthread.h>

int pthread_equal(pthread_t t1, pthread_t t2);

如果两个线程 ID t1 和 t2 相等,则 pthread_equal()返回一个非零值;否则返回 0。在 Linux 系统中,使用无符号长整型(unsigned long int)来表示 pthread_t 数据类型,但是在其它系统当中,则不一定是无符号长整型,所以我们必须将 pthread_t 作为一种不透明的数据类型加以对待,所以 pthread_equal()函数用于比较两个线程 ID 是否相等是有用的。

线程 ID 在应用程序中非常有用,原因如下:

  • 很多线程相关函数,譬如后面将要学习的 pthread_cancel()、pthread_detach()、pthread_join()等,它们都是利用线程 ID来标识要操作的目标线程;
  • 在一些应用程序中,以特定线程的线程 ID作为动态数据结构的标签,这某些应用场合颇为有用, 既可以用来标识整个数据结构的创建者或属主线程,又可以确定随后对该数据结构执行操作的具体线程。

 2.创建线程

启动程序时,创建的进程只是一个单线程的进程,称之为初始线程或主线程,本小节我们讨论如何创建一个新的线程。

主线程可以使用库函数 pthread_create()负责创建一个新的线程,创建出来的新线程被称为主线程的子线程,其函数原型如下所示:

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

使用该函数需要包含头文件<pthread.h>。

函数参数和返回值含义如下:

thread:

pthread_t 类型指针,当 pthread_create()成功返回时,新创建的线程的线程 ID 会保存在参数 thread所指向的内存中,后续的线程相关函数会使用该标识来引用此线程。

attr:

pthread_attr_t 类型指针,指向 pthread_attr_t 类型的缓冲区,pthread_attr_t 数据类型定义了线程的各种属性,关于线程属性将会在 11.8 小节介绍。如果将参数 attr 设置为 NULL,那么表示将线程的所有属性设置为默认值,以此创建新线程。

start_routine:

参数 start_routine 是一个函数指针,指向一个函数,新创建的线程从 start_routine()函数开始运行,该函数返回值类型为void *,并且该函数的参数只有一个void *,其实这个参数就是pthread_create()函数的第四个参数 arg。如果需要向 start_routine()传递的参数有一个以上,那么需要把这些参数放到一个结构体中,然后把这个结构体对象的地址作为 arg 参数传入。

arg:

传递给 start_routine()函数的参数。一般情况下,需要将 arg 指向一个全局或堆变量,意思就是说在线程的生命周期中,该 arg 指向的对象必须存在,否则如果线程中访问了该对象将会出现错误。当然也可将参数 arg 设置为 NULL,表示不需要传入参数给 start_routine()函数。

返回值:

成功返回 0;失败时将返回一个错误号,并且参数 thread 指向的内容是不确定的。

注意 pthread_create()在调用失败时通常会返回错误码,它并不像其它库函数或系统调用一样设置 errno,每个线程都提供了全局变量 errno 的副本,这只是为了与使用 errno 到的函数进行兼容,在线程中,从函数中返回错误码更为清晰整洁,不需要依赖那些随着函数执行不断变化的全局变量,这样可以把错误的范围限制在引起出错的函数中。

线程创建成功,新线程就会加入到系统调度队列中,获取到 CPU 之后就会立马从start_routine()函数开始运行该线程的任务;调用 pthread_create()函数后,通常我们无法确定系统接着会调度哪一个线程来使用CPU 资源,先调度主线程还是新创建的线程呢(而在多核 CPU 或多 CPU 系统中,多核线程可能会在不同的核心上同时执行)?如果程序对执行顺序有强制要求,那么就必须采用一些同步技术来实现。这与前面学习父、子进程时也出现了这个问题,无法确定父进程、子进程谁先被系统调度。

使用示例使用 pthread_create()函数创建一个除主线程之外的新线程,示例代码如下所示:

//示例代码 11.3.1 pthread_create()创建线程使用示例
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>

static void *new_thread_start(void *arg) {
	 printf("新线程: 进程 ID<%d> 线程 ID<%lu>\\n", getpid(), pthread_self());
	 return (void *)0; 
}

int main(void) {
	 pthread_t tid;
	 int ret;
	 ret = pthread_create(&tid, NULL, new_thread_start, NULL);

	 if (ret) {
			 fprintf(stderr, "Error: %s\\n", strerror(ret));
			 exit(-1);
	 }

	 printf("主线程: 进程 ID<%d> 线程 ID<%lu>\\n", getpid(), pthread_self());
	 sleep(1);
	 exit(0);
}

应该将 pthread_t 作为一种不透明的数据类型加以对待,但是在示例代码中需要打印线程 ID,所以要明确其数据类型,示例代码中使用了 printf()函数打印线程 ID 时,将其作为 unsigned long int 数据类型,在 Linux系统下,确实是使用 unsigned long int 来表示 pthread_t,所以这样做没有问题!

主线程休眠了 1 秒钟,原因在于,如果主线程不进行休眠,它就可能会立马退出,这样可能会导致新创建的线程还没有机会运行,整个进程就结束了。

在主线程和新线程中,分别通过 getpid()和 pthread_self()来获取进程 ID 和线程 ID,将结果打印出来,运行结果如下所示:

编译时出现了错误,提示“对‘pthread_create’未定义的引用”,示例代码确实已经包含了<pthread.h>头文件,但为什么会出现这样的报错,仔细看,这个报错是出现在程序代码链接时、而并非是编译过程,所以可知这是链接库的文件,如何解决呢?

gcc -o testApp testApp.c -lpthread

使用-l 选项指定链接库 pthread,原因在于 pthread 不在 gcc 的默认链接库中,所以需要手动指定。再次编译便不会有问题了,如下:

从打印信息可知,正如前面所介绍那样,两个线程的进程 ID 相同,说明新创建的线程与主线程本来就属于同一个进程,但是它们的线程 ID 不同。从打印结果可知,Linux 系统下线程 ID 数值非常大,看起来像是一个指针。

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

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

相关文章

【Linux进程间通信】Linux匿名管道详解:构建进程间通信的隐形桥梁

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ ⏩收录专栏⏪&#xff1a;Linux “ 登神长阶 ” &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀Linux进程间通信 &#x1f4d2;1. 进程间通信介绍&#x1f4da;2. 什么是管道&#x1f4dc;3…

22.1 k8s不同role级别的服务发现

本节重点介绍 : 服务发现的应用3种采集的k8s服务发现role 容器基础资源指标 role :nodek8s服务组件指标 role :endpoint部署在pod中业务埋点指标 role :pod 服务发现的应用 所有组件将自身指标暴露在各自的服务端口上&#xff0c;prometheus通过pull过来拉取指标但是promet…

期权卖方如何选择铁矿石行权价?期权策略盈亏分析计算方式详解

截止9月30日收盘&#xff0c;铁矿石2411合约收盘价825元/吨。日线级别处于上涨趋势中 假设以825元为最新价&#xff0c;假设后市铁矿石期货价格会下跌&#xff0c;期权卖方应该如何选择行权&#xff1f; 卖出行权价800的看涨期权&#xff0c;期权报价37.9&#xff0c;一手权利…

【环境配置】科研小白Windows下安装Git

2024年小白使用Win10安装Git 2.46.2教程&#xff1a; 1 下载安装包 访问下载地址 Git - Downloading Package (git-scm.com) 下载之后打开文件 2 安装过程 点击Next 2.1 选择安装路径 2.2 选择勾选必要组件 2.3 一路Next 这一步直接Next即可 继续点击Next 继续点击Ne…

Linux学习之路 -- 线程 -- 线程池

前面介绍了条件变量的生产消费模型&#xff0c;下面介绍一下条件变量的另一个用法&#xff0c;那就是线程池。线程池的用法其实就是先创建一批线程&#xff0c;然后让这些线程从任务队列中取数据。具体就是生产消费者模型&#xff0c;(我的代码中生产线程只有一个并且生产的任务…

自动微分-梯度!

前言背景知识&#xff1a; 梯度下降(Gradient descent,GD) 正文&#xff1a; 自动微分为机器学习、深度学习神经网络的核心知识之一&#xff0c;若想更深一步使用神经网络进行具体问题研究&#xff0c;那么自动微分不得不了解。 “工欲善其事&#xff0c;必先利其器”&…

数据结构 ——— 单链表oj题:合并两个升序链表

目录 题目要求 手搓两个简易链表 代码实现 题目要求 将两个升序链表合并为一个新的升序链表并返回&#xff0c;新链表是通过拼接给定的两个链表的所有节点组成的 手搓两个简易链表 代码演示&#xff1a; struct ListNode* n1 (struct ListNode*)malloc(sizeof(struct …

【Linux】第一个小程序——进度条实现

&#x1f525; 个人主页&#xff1a;大耳朵土土垚 &#x1f525; 所属专栏&#xff1a;Linux系统编程 这里将会不定期更新有关Linux的内容&#xff0c;欢迎大家点赞&#xff0c;收藏&#xff0c;评论&#x1f973;&#x1f973;&#x1f389;&#x1f389;&#x1f389; 文章目…

【Python报错已解决】TypeError: ‘NoneType‘ object is not iterable

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 专栏介绍 在软件开发和日常使用中&#xff0c;BUG是不可避免的。本专栏致力于为广大开发者和技术爱好者提供一个关于BUG解决的经…

Android KMP 快速入门1 - 项目打包

这里写目录标题 KMP 运行与打包运行程序程序打包 KMP 运行与打包 运行程序 运行Android客户端&#xff0c;你首先需要把USB连接到物理机上&#xff0c;或者使用模拟器模拟一个手机&#xff1b; 然后选择运行配置的 composeApp &#xff0c;运行它即可 运行windows客户端&…

Qt/C++开源控件 自定义雷达控件

使用Qt框架创建一个简单的雷达图&#xff0c;包含动态扫描、目标点生成、刻度和方向标识。代码实现使用C编写&#xff0c;适合用作学习和扩展的基础。 1. 头文件与基本设置 #include "RadarWidget.h" #include <QPainter> #include <QPen> #include &…

解决银河麒麟操作系统V10软件包架构不符问题

TOC &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在银河麒麟桌面操作系统V10中安装软件包时&#xff0c;如果遇到“软件架构与本机架构不符”的提示&#xff0c;可以尝试以下步骤来解决问题&#xff1a; 1. 确认架构一致性 查看本机架构…

基于STM32的智能门禁系统

目录 引言项目背景环境准备 硬件准备软件安装与配置系统设计 系统架构关键技术代码示例 密码验证模块电机控制实现门禁状态监控应用场景结论 1. 引言 智能门禁系统能够通过密码或其他验证方式&#xff08;如指纹、刷卡等&#xff09;控制门的开关&#xff0c;结合电机控制与…

2024年7月大众点评武汉餐饮美食店铺基础信息

在做一些城市分析、学术研究分析、商业选址、商业布局分析等数据分析挖掘时&#xff0c;大众点评的数据参考价值非常大&#xff0c;截至2024年7月&#xff0c;大众点评美食店铺剔除了暂停营业、停止营业后的最新数据情况分析如下。 武汉餐饮美食店铺约9.6万家&#xff0c;有均…

MySQL高阶2051-商店中每个成员的级别

目录 题目 准备数据 分析数据 实现 总结 题目 一个商店想对其成员进行分类。有三个层次: "钻石": 如果转换率 大于或等于 80."黄金": 如果转换率 大于或等于 50 且小于 80."白银": 如果转化率 小于 50."青铜": 如果该成员从未访…

澳洲本科毕业论文的初稿撰写要点分析

临近毕业季的时候&#xff0c;如何更好地完成澳洲本科毕业论文成为了困扰大家的一大难题。澳洲毕业论文的质量高低关系到留学生能否顺利毕业。因此大家都会关心如何更好地完成毕业论文。我们在之前一些文章中介绍了如何确立论点&#xff0c;如何查找资料以及如何完成高质量的di…

HarmonyOS/OpenHarmony 如何将rawfile中文件复制到沙箱中

关键词&#xff1a;h5离线加载、HarmonyOS、OpenHarmony、文件操作、复制、解压 当下有一个场景&#xff0c;需要离线加载 h5离线资源zip包&#xff0c;并实现资源包的动态更新&#xff0c;那么仅靠 $rawfile并不能实现该功能&#xff0c;那么我们该如何实现&#xff1f; 我们…

面试题05.08绘制直线问题详解(考察点为位运算符)

目录 一题目&#xff1a; 二详细思路汇总&#xff1a; 三代码解答&#xff08;带注释版&#xff09;&#xff1a; 一题目&#xff1a; leetcode原题链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 二详细思路汇总&#xff1a; 这里先剧透一下简单版思路哦&…

Azure DevOps Server:不能指派新增的用户

Contents 1. 概述2. 解决方案 1. 概述 近期和微软Azure DevOps项目组解决了一个“无法指派开发人员”的问题&#xff0c;在此分享给大家。问题描述&#xff1a; 在一个数据量比较大的Azure DevOps Server的部署环境中&#xff0c;用户发现将新用户的AD域账户添加到Azure DevOps…