2-6-1-1 QNX编程入门之进程和线程(四)

阅读前言

本文以QNX系统官方的文档英文原版资料“Getting Started with QNX Neutrino: A Guide for Realtime Programmers”为参考,翻译和逐句校对后,对在QNX操作系统下进行应用程序开发及进行资源管理器编写开发等方面,进行了深度整理,旨在帮助想要了解QNX的读者及开发者可以快速阅读,而不必查看晦涩难懂的英文原文,这些文章将会作为一个或多个系列进行发布,从遵从原文的翻译,到针对某些重要概念的穿插引入,以及再到各个重要专题的梳理,大致分为这三个层次部分,分不同的文章进行发布,依据这样的原则进行组织,读者可以更好的查找和理解。


1. 进程和线程

1.3. 线程和进程

2-6-1-1 QNX编程入门之进程和线程(一)

2-6-1-1 QNX编程入门之进程和线程(二)

2-6-1-1 QNX编程入门之进程和线程(三)

接前面章节内容继续。

1.3.3. 启动一个线程

既然我们已经了解了如何启动另一个进程,那么让我们来看看如何启动另一个线程。

任何线程都可以在同一进程中创建另一个线程,没有任何限制(当然,除了内存空间!)。最常见的方法是通过POSIX pthread_create() 调用:

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

pthread_create() 函数需要四个参数:

  • thread,指向存储线程 ID 的 pthread_t 的指针。
  • attr,属性结构。
  • start_routine,线程开始的例程。
  • arg,传递给线程的 start_routine 例程的参数。

请注意, 线程指针(thread)和属性结构体(attr )是可选的,你可以将它们传递为 NULL。
参数 thread 可用于存储新建线程的线程 ID。你会注意到,在下面的示例中,我们将传递一个 NULL,这意味着我们并不关心新创建线程的 ID 是什么。如果我们关心的话,可以这样做:

pthread_t tid;
pthread_create (&tid, …);
printf ("Newly created thread id is %d\n", tid);

这种用法其实很典型,因为你通常会想要知道哪个线程 ID 正在运行哪段代码。

这里有一个微妙的小问题。新创建的线程有可能在线程 ID(参数 tid 指针指向的内容)被填充之前就已开始运行,这意味着在将 tid 用作全局变量时一定要小心这一点。上面所显示的用法没有问题,因为 pthread_create() 调用已返回,这意味着 tid 值已正确填入。

新线程开始执行 start_routine(),参数为 arg。

1.3.3.1. 线程属性结构体

当你启动一个新线程时,它可以采用一些定义明确的默认值,或者你也可以显式地指定其特性。
在我们深入探讨线程属性函数之前,先来看看 pthread_attr_t 数据类型:

typedef struct {int                 __flags;size_t              __stacksize;void                *__stackaddr;void                (*__exitfunc)(void *status);int                 __policy;struct sched_param  __param;unsigned            __guardsize;
} pthread_attr_t;/* 
这些字段的基本使用方法如下: 
__flags
非数字(布尔)特性(例如,线程应 "可分离 detached" 运行还是 "可连接 joinable" 运行)。stacksize、 stackaddr 和 guardsize
堆栈的规格。__exitfunc
线程退出时执行的函数。__policy and __param
调度相关参数。
*/

以下是可供使用的函数:

属性管理相关:

  • pthread_attr_destroy()
  • pthread_attr_init()

标志(布尔特征)相关:

  • pthread_attr_getdetachstate()
  • pthread_attr_setdetachstate()
  • pthread_attr_getinheritsched()
  • pthread_attr_setinheritsched()
  • pthread_attr_getscope()
  • pthread_attr_setscope()
  • pthread_attr_getsuspendstate_np()
  • pthread_attr_setsuspendstate_np()

堆栈相关 :

  • pthread_attr_getguardsize()
  • pthread_attr_setguardsize()
  • pthread_attr_getstack()
  • pthread_attr_setstack()
  • pthread_attr_getstackaddr()
  • pthread_attr_setstackaddr()
  • pthread_attr_getstacklazy()
  • pthread_attr_setstacklazy()
  • pthread_attr_getstackprealloc()
  • pthread_attr_setstackprealloc()
  • pthread_attr_getstacksize()
  • pthread_attr_setstacksize()

调度相关:

  • pthread_attr_getschedparam()
  • pthread_attr_setschedparam()
  • pthread_attr_getschedpolicy()
  • pthread_attr_setschedpolicy()


这个列表看起来很长,但实际上我们只需要关心其中一半,因为它们是以 "get "和 "set" 的形式成对出现的(不过,pthread_attr_init()pthread_attr_destroy() 除外) 。

在研究属性相关函数之前,有一点需要注意。在使用属性结构体之前,必须调用 pthread_attr_init() 对其进行初始化, 然后使用相应的 pthread_attr_set*() 函数对其进行设置,最后再调用 pthread_create() 函数进行线程的创建。 在线程创建之后再更改属性结构体的内容是没有效果的。
 

1.3.3.1.1. 线程属性管理

在使用属性结构体之前,必须调用 pthread_attr_init() 函数对其进行初始化。

...
pthread_attr_t  attr;
...
pthread_attr_init (&attr);

你可以调用 pthread_attr_destroy() 函数来 “反初始化” 线程属性结构体,但几乎没人会这么做(除非你的代码要遵循 POSIX 标准)。

在接下来的描述中,我已用 “(default)” 标记出了默认值。

1.3.3.1.2. 线程属性“flags”

让我们从布尔型线程属性开始说起。

  • 要创建一个 “可连接(joinable)” 的线程(意思是另一个线程能够通过 pthread_join() 函数与该线程的“终止”进行同步),你可以像下面这样:
(default)
pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_JOINABLE);

要创建一个无法被连接的线程(称为 "detached" 线程),可以使用下面的方法:

pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);

  • 如果希望线程继承创建者线程的调度属性(即具有相同的调度策略和优先级),你可以这样:
(default)
pthread_attr_setinheritsched (&attr, PTHREAD_INHERIT_SCHED);

要创建一个由属性结构体本身指定调度属性的线程(使用 pthread_attr_setschedparam()pthread_attr_setschedpolicy() 进行调度属性设置),可以使用下面的方法:

pthread_attr_setinheritsched (&attr, PTHREAD_EXPLICIT_SCHED);

  • 在 QNX Neutrino 7.0.1 或更高版本中,如果你不希望线程在创建时就进入挂起状态(suspended state),可以像下面这样:
(default)
pthread_attr_setsuspendstate_np (&attr, PTHREAD_NOT_SUSPENDED);

如果你希望线程在创建时就处于挂起状态(suspended),你可以使用下面这个方式:

pthread_attr_setsuspendstate_np(&attr, PTHREAD_SUSPENDED);

  • 最后,你永远不会调用 pthread_attr_setscope()。为什么?因为 QNX Neutrino 只支持 "system" 作用域,这也是属性结构体初始化时的默认值。(“system”作用域意味着所有线程都在系统中互相竞争 CPU;而另一个值“process”则意味着线程在进程内相互竞争 CPU,而内核则对进程进行调度)。 如果你坚持要调用,只能按如下方式进行调用:
(default)
pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM);

1.3.3.1.3. 线程属性“stack”

设置线程属性堆栈参数的函数如下:

int
pthread_attr_setstack (pthread_attr_t *attr, void *addr, size_t ssize );int
pthread_attr_setstackaddr (pthread_attr_t *attr, void *addr);int
pthread_attr_setguardsize (pthread_attr_t *attr, size_t gsize);int
pthread_attr_setstacklazy (pthread_attr_t *attr, int lazystack);int
pthread_attr_setstackprealloc (pthread_attr_t * attr, size_t psize);int
pthread_attr_setstacksize (pthread_attr_t *attr, size_t ssize);

这些函数都将属性结构作为它们的第一个参数;其他参数则从以下各项中选取:

addr栈的地址(如果由你提供栈地址的话)。

gsize“保护(guard)” 区域的大小。

lazystack指示栈是应该按需从物理内存中分配,还是预先分配。

psize要为线程的 MAP_LAZY 栈预分配的内存量。

ssize栈的大小。

保护区域(guard area)是紧挨着栈(stack)的一块内存区域,线程不能对其进行写入操作。如果线程对其进行了写入(意味着栈即将溢出),那么线程将会收到 SIGSEGV 信号。如果保护区域大小(guardsize)为 0,则表示不存在保护区域。这也意味着不会进行栈溢出检查。如果 guardsize 不为 0,那么它至少会被设置为系统范围内的默认保护区域大小(你可以通过调用 sysconf() 函数,并传入常量 _SC_PAGESIZE 来获取该默认大小)。需要注意的是,保护区域大小至少会和一个“页面”一样大(例如,在 x86 处理器上是 4KB)。另外,还要注意保护区域的“页面”并不会占用任何实际的物理内存,它只是通过虚拟地址(内存管理单元,即 MMU)的 “技巧” 来实现的。

addr 是栈的地址(如果你提供了的话)。你可以将其设置为 NULL,这意味着需要系统将为线程进行分配(并且会自动释放!)栈。指定栈的好处在于你可以事后对栈的深度进行分析。具体做法是分配一个栈区域,用一个“标识”(例如,反复重复的字符串“STACK”)填充它,然后让线程运行。当线程运行结束后,你查看栈区域,看看线程覆盖你的标识到了什么程度,这样就能得知在这次特定运行过程中所使用的栈的最大深度。

ssize 参数用于指定栈的大小。如果你通过 addr 提供了栈,那么 ssize 就应该是该数据区域的大小。如果你没有通过 addr 提供栈(也就是传递了 NULL),那么 ssize 参数会告知系统应该为你分配多大的栈。如果你将 ssize 指定为 0,系统会为你选择默认的栈大小。显然,将 ssize 指定为 0 同时又通过 addr 指定一个栈的地址,这种做法很不好。这样做,实际上相当于在说“这是一个指向某个对象的指针,而这个对象的大小是某个默认值”。 但是,问题在于你所指定的对象大小和传递的地址值之间没有关联。

如果通过 addr 提供了栈,那么对于该线程来说不存在自动的栈溢出保护机制(即不存在保护区域)。不过,你当然可以自己使用 mmap()mprotect() 函数来设置相关保护机制。

最后,lazystack 参数用于指示物理内存是应该按需分配(使用值 PTHREAD_STACK_LAZY)还是预先全部分配(使用值 PTHREAD_STACK_NOTLAZY)。按需分配栈(根据需要分配)的好处在于线程不会占用比实际所需更多的物理内存。预先全部分配方法的好处在于,在内存不足的环境中,线程在运行期间需要额外的栈空间但又没有剩余内存时,不会莫名其妙地终止运行。如果你使用后一种方法(即 PTHREAD_STACK_NOTLAZY),你很可能会想要设置栈的实际大小,而不是采用默认大小,因为默认大小通常是比较大的。

1.3.3.1.4. 线程属性“sheduling”

最后,如果你确实为 pthread_attr_setinheritsched() 函数指定了 PTHREAD_EXPLICIT_SCHED 参数,那么你就需要一种方法来指定即将创建的线程的调度策略以及优先级。

可以通过以下两个函数来实现:

int
pthread_attr_setschedparam (pthread_attr_t *attr,const struct sched_param *param);
int
pthread_attr_setschedpolicy (pthread_attr_t *attr,int policy);

调度策略很简单,它取值为 SCHED_FIFOSCHED_RRSCHED_OTHER 其中之一。

目前,SCHED_OTHER 也是被映射为 SCHED_RR

param 是一个结构体,这里与之相关的成员只有一个:sched_priority。通过直接赋值的方式将该值设置为期望的优先级即可。

需要留意的一个常见错误是指定了 PTHREAD_EXPLICIT_SCHED 参数后,却只设置了调度策略。问题在于,在一个已初始化的属性结构中,param.sched_priority 的值为 0。这个优先级与空闲进程的优先级相同,这意味着你新创建的线程将会与空闲进程竞争 CPU 资源。这种情况我亲身经历过,印象深刻呢。(偷笑)

已经有很多人被这个问题困扰过了,所以优先级 0 是预留给空闲线程的。你根本不能让一个线程以优先级 0 来运行。

1.3.3.2. 几个例子

让我们来看一些示例。我们假定已经包含了合适的头文件(<pthread.h><sched.h>),并且即将要创建的线程名为 new_thread(),它的函数原型声明和定义都是正确的。

创建线程最常见的方式就是简单地使用默认值:

pthread_create (NULL, NULL, new_thread, NULL);

在上述示例中,我们使用默认值创建了新线程,并给它传递了一个 NULL 作为其唯一的参数(也就是上面 pthread_create() 调用中的第三个 NULL)。

通常来说,你可以通过参数(arg 字段)给新线程传递任何你想要传递的内容。在这里我们传递数字 123

pthread_create (NULL, NULL, new_thread, (void *) 123);

下面是一个更复杂些的示例,创建一个不可结合的(non-joinable)线程,该线程采用轮转调度算法且优先级为 15

pthread_attr_t attr;// 初始化属性结构
pthread_attr_init (&attr);// 将分离状态设置为“分离”
pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);// 覆盖默认的继承调度属性设置
pthread_attr_setinheritsched (&attr, PTHREAD_EXPLICIT_SCHED);
pthread_attr_setschedpolicy (&attr, SCHED_RR);
attr.param.sched_priority = 15;// 最后,创建线程
pthread_create (NULL, &attr, new_thread, NULL);

要看看一个多线程程序 “是什么样子的”,你可以在 shell 中运行 pidin 命令。假设我们的程序名为 spud。如果我们在 spud 创建线程之前运行一次 pidin 命令,然后在 spud 又创建了两个线程之后(总共三个线程)再运行一次 pidin 命令,输出结果看起来会是这样的(我对 pidin 的输出进行了简化,只展示 spud 的相关内容):

# pidin
pid    tid name               prio STATE       Blocked
12301   1 spud                10r READY
# pidin
pid    tid name               prio STATE       Blocked
12301   1 spud                10r READY
12301   2 spud                10r READY
12301   3 spud                10r READY

如你所见,进程 spud(进程 ID 为 12301)有三个线程(在 “tid” 列下可以看到)。这三个线程以优先级 10 运行,采用轮转调度算法(由 10 后面的 “r” 表示)。这三个线程都处于 “READY” 状态,这意味着它们能够使用 CPU,但当前并没有在 CPU 上运行(当前有另一个优先级更高的线程正在运行)。

既然我们已经了解了创建线程的所有相关内容,现在让我们来看看如何以及在哪些地方会用到线程吧。

1.3.3.3. 在这里使用线程是个好主意

有两类问题比较适合运用线程来解决。

线程就好比是 C++ 中的运算符重载;在当时看来,用一些有意思的用法去重载每一个运算符似乎是个好主意,但这会让代码变得难以理解。线程方面也是类似的情况,你可以创建大量的线程,然而额外增加的复杂性会使你的代码难以理解,进而难以维护。另一方面,明智地使用线程,则会让代码在功能上显得非常简洁明了。

在能够并行化操作的情况下,线程的作用很大,能让人马上想到很多数学问题(比如图形处理、数字信号处理等等)。另外,当你希望一个程序在共享数据的同时执行多个独立功能时,线程也非常有用,例如一个同时为多个客户端提供服务的网络服务器就是如此。我们接下来会对这两类情况进行探讨。


未完待续,请继续关注本专栏内容……

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

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

相关文章

【Java项目】基于SpringBoot的【外卖点餐系统】

【Java项目】基于SpringBoot的【外卖点餐系统】 技术简介&#xff1a;本系统使用JSP技术&#xff0c;采用B/S架构、Spring Boot框架、MYSQL数据库进行开发设计。 系统简介&#xff1a;管理员&#xff1b;首页、个人中心、用户管理、商家管理、菜品分类管理、骑手管理、系统管理…

Spring Boot教程之三十九: 使用 Maven 将 Spring Boot 应用程序 Docker 化

如何使用 Maven 将 Spring Boot 应用程序 Docker 化&#xff1f; Docker是一个开源容器化工具&#xff0c;用于在隔离环境中构建、运行和管理应用程序。它方便开发人员捆绑其软件、库和配置文件。Docker 有助于将一个容器与另一个容器隔离。在本文中&#xff0c;为了将Spring B…

计算机网络|数据流向剖析与分层模型详解

文章目录 一、网络中的数据流向二、计算机网络通信模型1.OSI 模型2.TCP/IP 模型3.TCP/IP五层模型3.1 分层架构描述3.2各层地址结构3.3UDP数据包报头结构 三、总结 一、网络中的数据流向 在计算机网络中&#xff0c;数据的流向是指数据从发送端到接收端的传输路径。数据流向涉及…

uniapp——微信小程序,从客户端会话选择文件

微信小程序选择文件 文章目录 微信小程序选择文件效果图选择文件返回数据格式 API文档&#xff1a; chooseMessageFile 微信小程序读取文件&#xff0c;请查看 效果图 选择文件 /*** description 从客户端会话选择文件* returns {String} 文件路径*/ const chooseFile () &g…

vue3+vite+nginx打包

在开发环境下&#xff0c;已经可以正常地运行一个有增删改查功能的页面了&#xff0c;但如何把它发布到运行服务器呢&#xff1f;仍有许多的问题需要探索。 网上很多文章给了很大的帮助&#xff0c;但总是没有说明原理&#xff0c;对于像我这样的初学者来说&#xff0c;不知其…

CAN201 Introduction to Networking(计算机网络)Pt.2 传输层

文章目录 3. Transport Layer&#xff08;传输层&#xff09;3.1 Multiplexing and demultiplexing&#xff08;多路复用和多路分解&#xff09;3.2 Connectionless transport&#xff1a;UDP3.3 Principles of reliable data transfer3.4 Pipelined communication3.5 TCP: con…

JVM 详解

一. JVM 内存区域的划分 1. 程序计数器 程序计数器是JVM中一块比较小的空间, 它保存下一条要执行的指令的地址. [注]: 与CPU的程序计数器不同, 这里的下一条指令不是二进制的机器语言, 而是Java字节码. 2. 栈 保存方法中的局部变量, 方法的形参, 方法之间的调用关系. 栈又…

Docker--Docker Network(网络)

Docker为什么需要网络管理 容器的网络默认与宿主机及其他容器之间是隔离的,但有时也需要与宿主机和容器进行通信。 容器间及容器与外部通信的需求 容器间通信&#xff1a;在Docker环境中&#xff0c;容器默认与宿主机及其他容器是相互隔离的。然而&#xff0c;在实际应用中&…

深入浅出 MyBatis | CRUD 操作、配置解析

3、CRUD 3.1 namespace namespace 中的包名要和 Dao/Mapper 接口的包名一致&#xff01; 比如将 UserDao 改名为 UserMapper 运行发现抱错&#xff0c;这是因为 UserMapper.xml 中没有同步更改 namespace 成功运行 给出 UserMapper 中的所有接口&#xff0c;接下来一一对…

UE5材质节点Panner

Panner节点可以让贴图动起来&#xff0c;快捷键是P&#xff0c;Speed的数值大小就是贴图移动的快慢&#xff0c;x和y是方向 这个节点可以用来做&#xff0c;传送带&#xff0c;护盾&#xff0c;河流&#xff0c;岩浆&#xff0c;瀑布等 制作岩浆流动效果 创建材质&#xff0c;…

Kimi进行学术方向选择精讲!

目录 1.文献搜索 2.辅助选题 3.选题判断 在我们之前的文章中&#xff0c;小编都强调了选题在文章价值中的核心作用。一篇优秀的文章背后&#xff0c;肯定有一个精心挑选的选题。选题的好坏直接影响着文章能够发表的期刊等级。许多宝子们却采取了相反的做法&#xff0c;将大量…

Google Veo AI 视频生成工具评测:AI视频生成的未来已来

近年来&#xff0c;AI技术的迅猛发展使得我们对虚拟现实的认知不断发生改变&#xff0c;尤其是在视频生成领域。Google 最新推出的 Veo AI 视频生成工具&#xff0c;无疑为这一领域带来了前所未有的突破。通过与其他知名工具如 OpenAI 的 Sora 进行对比&#xff0c;Veo 显示出令…

idea 8年使用整理

文章目录 前言idea 8年使用整理1. 覆盖application配置2. 启动的时候设置编辑空间大小&#xff0c;并忽略最大空间3. 查询类的关系4. 查看这个方法的引用关系5. 查看方法的调用关系5.1. 查看被调用关系5.2. 查看调用关系 6. 方法分隔线7. 选择快捷键类型8. 代码预览插件9. JReb…

3.微服务灰度发布落地实践(组件灰度增强)

文章目录 前言调用链示图dubbo服务之间的的调链cloud 服务之间的调用链 网关servlet容器: 标签续传1.定义插件2.实现灰度增强拦截 线程池: 标签续传1.拦截Runnable或Callable,接口增强实现标签续传;Callable 插件定义Runnable 插件定义拦载Callabl或Runnable构造(可共用)拦载ru…

java基础知识22 java的反射机制

一 java反射机制 1.1 概述 Java反射&#xff0c;程序在运行时&#xff0c;动态获取类信息&#xff08;类元数据&#xff09;&#xff0c;获取字段属性&#xff0c;动态创建对象和调用方法。Spring框架正是基于反射机制&#xff0c;通过我们的配置文件&#xff0c;在项目运行时…

[羊城杯 2024]Check in

下载附件&#xff0c;解压的时候发现注释&#xff1a;5unVpeVXGvjFah 解压得到的flag.txt文件内容如下&#xff1a; 注释5unVpeVXGvjFah放到随波逐流中一键解码发现base58解码得出一个正常点的字符串&#xff1a;Welcome2GZ&#xff0c;应该是某个key&#xff1f; 去hex解码&am…

C++Primer 控制流

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…

RJ45网口模块设计

1、以太网概述及RJ45实物 2、常用网口信号介绍 3、RJ45网口布局布线要点分析 4、总结 1、变压器下面需要进行挖空处理&#xff0c;以免底下的铜引入干扰&#xff0c;&#xff08;将多边形挖空区域的所在层设置为Multi-Layer多层&#xff09; 2、为了更直观的看一个类中线的长…

Metagenome宏基因组,未识别的物种unclassified

1&#xff1a;"Bacteria|Proteobacteria|Betaproteobacteria|Neisseriales|Neisseriaceae|Kingella" 2&#xff1a;"Bacteria|Proteobacteria|Betaproteobacteria|Neisseriales|Neisseriaceae|unclassified" # 未识别到的/未注解到的…

深度学习笔记1:神经网络与模型训练过程

参考博客&#xff1a;PyTorch深度学习实战&#xff08;1&#xff09;——神经网络与模型训练过程详解_pytorch 实战-CSDN博客 人工神经网络 ANN&#xff1a;张量及数学运算的集合&#xff0c;排列方式近似于松散的人脑神经元排列 组成 1&#xff09;输入层 2&#xff09;隐…