Pthread编程——理论与实践
文章平均质量分 50
Pthreads是遵POSIX 1003.1c标准的多线程编程规范,基于Pthreads的多线程编程可最大限度地发挥处理器多核多线程的能力,从而开发出高性能的应用。本专栏围绕着Pthreads展开,从API的详细介绍到使用Pthreads进行开发实战,助你入门多线程编程。
Mega_Li
在图像处理领域工作多年,具有较丰富的算法开发与工程落地经验
展开
-
1. Pthreads专栏简介
以前硬件供应商一般都会提供相应硬件专用的线程库,使得代码的可移植性成为另开发者头疼的一个问题。在UNIX系统中,IEEE POSIX 1003.1c标准已经定义了基于C的标准化线程编程接口规范,遵守该规范实现的线程库称为POSIX threads,或者Pthreads。本专栏围绕着Pthreads展开,从API的详细介绍到使用Pthreads进行开发实战,助你入门多线程编程。教程首先介绍了线程的概念,产生的背景和设计思想,之后围绕着线程管理,互斥变量,和条件变量展开。原创 2023-11-07 20:46:41 · 262 阅读 · 0 评论 -
9. 使用Pthreads实现线程池(二)
首先定义存储任务信息的结构体,我们可以将其抽象为函数指针和参数,如下所示// 函数指针void *arg;// 函数参数接下来定义线程池的结构体,包含线程池的资源和状态等属性,如下所示// 工作线程数量// 任务队列的容量// 加入的任务数量// 将要保存任务信息的队列索引// 将要取出任务信息的队列索引// 线程池状态变量操作的互斥锁// 任务队列有空闲空间的信号// 任务队列中有待处理任务的信号// 工作线程数组指针// 任务队列,环形缓冲区形式// 销毁标志。原创 2023-12-07 20:52:50 · 292 阅读 · 0 评论 -
9. 使用Pthreads实现线程池(一)
线程池初始化时也会创建一个管理线程,它的主要职责是监视工作负载状态,动态调整线程数量来尽量快速地处理任务,同时尽量降低资源的无效占用。基本思想就是提前创建一些工作线程,将加入的任务分配给它们做处理,并根据任务/线程的数量情况动态地增删线程,确保任务处理效率和系统资源占用之间达到比较好的平衡。这里我们定义一个简化版本的线程池,其调用流程如下:构造线程池——加入任务——开始执行——销毁线程池。在接收到有任务要处理的信号后被唤醒并竞争任务,竞争到任务的线程做具体处理,没有竞争到任务的线程则会继续进入休眠。原创 2023-12-07 19:54:28 · 514 阅读 · 0 评论 -
8.3 补充练习(三)
当然实际代码中不推荐在线程中申请过大的临时变量(占用栈空间),如果有使用需求建议使用系统函数(malloc等)从进程的堆上分配空间,即double* A = (double*)malloc(sizeof(double) * ARRAY_SIZE),使用完毕后再释放。一种修改方法如注释的代码所示,可以使用pthread_attr_setstacksize()重新设置线程堆栈,使其满足使用要求。,即我们调用pthread_attr_getstacksize()获得的堆栈信息。上述代码执行会报错,其原因在于。原创 2023-11-07 20:27:37 · 121 阅读 · 0 评论 -
8.2 补充练习(二)
如果我们仿照hello_args.c中的做法,将BusyWork()中pthread_exit()的入参由(void*)t改为(void*)&t,同时将main()中的打印传参由(long)status改为*(long*)status,会发现打印出的status值是错误的。相比join.c,这里显示创建了分离状态的子线程,因此主线程也没有调用pthread_join()来连接子线程;主线程通过调用pthread_join()等待子线程结束执行,并接收了子线程中pthread_exit()传递的参数。原创 2023-11-07 20:25:51 · 106 阅读 · 0 评论 -
8.1 补充练习(一)
回答:hello.c中使用值传递的形式,t当时的值会产生拷贝副本保存在子线程对应的堆栈内,t之后的变化不会影响子线程的使用;而hello_args.c中传给子线程的是t的指针,t之后的变化会影响到访问它的子线程。这是一个错误的线程参数传递调用示例,相比上面的hello.c这段代码只是将之前的值传递((void*)t)改为了指针传递((void*)&t),同时子线程接受参数时也做了相应的修改,但它是错误的,为何?如果我们将main()中的pthread_exit()屏蔽,运行后的输出如下。原创 2023-11-07 20:23:16 · 87 阅读 · 0 评论 -
7.4 条件变量示例
存在pthread_cond_wait()调用在pthread_cond_signal()之后的情况,此时可能会导致调用pthread_cond_wait()的线程一直被阻塞,因此在调用前需要判断附加条件是否满足。这个示例代码中展示了条件变量的用法,主线程创建了三个子线程,其中两个子线程会更新一个全局变量,第三个子线程会等待全局变量满足一定条件后开始执行操作。使用while而不是if做如上附加条件的判断,避免线程被误唤醒导致的一些问题。原创 2023-11-07 20:19:34 · 139 阅读 · 0 评论 -
7.3 等待条件变量/向条件变量发送信号
pthread_cond_wait()会阻塞调用该方法的线程,直到等待的条件变量接收到了信号。等待的条件变量接收到信号后,阻塞的线程会被唤醒,之前被释放的互斥量会再次被上锁。此时有可能某个线程对一些共享数据做了修改,使得实际上等待的条件不再被满足,而此时其他等待的线程已经跳过了相关的判断。需要在调用该方法前对配合使用的互斥量上锁,然后在该方法完成后对释放互斥量。在调用pthread_cond_signal()之后没有释放互斥量可能导致另一个调用了pthread_cond_wait()的线程无法被唤醒。原创 2023-11-07 20:19:12 · 113 阅读 · 0 评论 -
7.2 创建和销毁条件变量
入参attr用于设置条件变量的属性,只有一个可设置的属性:是否允许进程间共享,如果开启则其他进程的线程也可以识别到该条件变量。attr的类型为pthread_condattr_t,该参数也可设为NULL,此时会使用默认值。pthread_condattr_init()和pthread_condattr_destroy()用于创建和销毁条件变量属性对象。当一个条件变量不再被使用时应该调用pthread_cond_destroy()来销毁它。条件变量的类型为pthread_cond_t,必须在使用之前初始化。原创 2023-11-07 19:00:00 · 252 阅读 · 0 评论 -
7.1 条件变量概述
当没有条件变量时,编程人员可能需要让线程持续轮询(可能位于一个临界区内)来检查一些条件是否满足;条件变量是线程之间同步的另一种方法,互斥量通过控制线程访问数据的形式来实现同步,而条件变量通过类似于共享数据的值来实现线程间同步。一直处理任务,直到一个特定的条件需要被满足(譬如需要“计数值”达到某个大小)检查全局变量的值是否满足了线程A处理的要求,如果满足则对线程A发送信号。条件变量往往和互斥量一起使用,使用条件变量的调用顺序如下表所示。会自动使用原子操作释放互斥量,使其能够被线程B使用。原创 2023-11-07 09:41:28 · 97 阅读 · 0 评论 -
6.4 例程:使用互斥量
这个例程为使用多线程配合互斥量进行点乘计算,相关的数据通过全局变量的形式存在,因此可以被各个线程访问;每个线程会在相关数据的不同区域上进行处理,主线程等待子线程完成操作后,将最后的结果打印出来。原创 2023-11-05 17:28:24 · 130 阅读 · 0 评论 -
6.3 互斥量加锁和释放
线程可调用pthread_mutex_trylock()来尝试获取某个互斥量,如果该互斥量已经被其他线程加锁,方法会直接返回一个表示互斥量“正忙”的错误码。当多个线程同时访问共享数据时,获得互斥量的线程在完成相关操作后才能够调用该方法释放互斥量。线程可调用pthread_mutex_lock()来获取某个互斥量,如果该互斥量已经被其他线程加锁,则该线程将会被阻塞,直到互斥量被其他线程释放。问题:当多个线程同时等待一个被加锁的互斥量时,哪个线程会在互斥量被释放时首先获得它。互斥量被其他线程加锁。原创 2023-11-05 17:25:26 · 116 阅读 · 0 评论 -
6.2 创建和销毁互斥量
pthread_mutexattr_init()和pthread_mutexattr_destroy()用于创建和销毁互斥量属性对象;在一个互斥量不再被使用时应该调用pthread_mutex_destroy()将其销毁。参数attr用于设置互斥量的属性信息,类型为pthread_mutexattr_t,可设为NULL。互斥量的类型为pthread_mutex_t,必须在使用前初始化。注意不是所有的平台具体实现中都一定提供如上的互斥量的三种属性信息。线程共享:制定线程共享互斥量的相关信息。原创 2023-11-05 17:23:21 · 140 阅读 · 0 评论 -
6.1 互斥量概述
Pthreads中规定某个时刻只有一个线程能够拥有某个互斥量(或者说对该互斥量上锁),因此即使有多个线程尝试同时对某个互斥量上锁,只有一个线程会成功;此时只有该线程释放了互斥量后,其他的线程才有可能持有互斥量。注意需要由编程人员确保每个线程通过使用互斥量实现对共享数据的保护,例如如果有4的线程同时更新某个数据,但是只有一个线程使用了互斥量,那么数据仍有被意外修改的可能。当一些线程同时竞争加锁某个互斥量时,该调用是阻塞的,即没有获得互斥量的线程会阻塞住。其他的线程获得互斥量并加锁,重复上述操作。原创 2023-11-05 17:21:09 · 144 阅读 · 0 评论 -
5.5 其他方法
pthread_once会执行一次init_routine方法,程序中第一次调用该接口的线程将会执行init_routine方法,其他后续对pthread_once的调用都不会再执行其中的init_routine方法。pthread_self返回调用该方法的线程独有的,被系统分配的id;pthread_equal比较两个线程的id,如果不同返回0,否则返回非0值。注意由于线程的id对象为不透明的类型,我们无法直接使用C语言中的==来判断两个线程id是否相等,或者把id和其他的数值做比较。原创 2023-11-05 17:17:18 · 97 阅读 · 0 评论 -
5.4 堆栈管理
安全且可移植的程序中不会做默认堆栈够用的假设,而是调用pthread_attr_setstacksize为线程显式的分配足够多的堆栈。在线程堆栈被放置于内存的特定区域的系统中,应用程序可以调用pthread_attr_getstackaddr和pthread_attr_setstackaddr 方法获取/设置相关信息。POSIX标准没有给出线程堆栈大小的明确规定,这依赖于平台的具体实现,是有可能不同的。线程能够获取的最大的堆栈大小也会不同,这可能和每个架构平台的线程数量有关。原创 2023-11-05 17:14:57 · 128 阅读 · 0 评论 -
5.3 连接和分离线程
如果相关线程调用了pthread_exit(),编程人员能够在主线程中获取相关线程终止时的具体信息(即pthread_exit()的参数会传递给pthread_join())。线程被创建时,它的一个属性决定其是能够被连接的或者是已经分离的,只有属性为能够被连接的线程才可以通过调用pthread_join() 对其进行连接。如果一个线程的属性为已分离的,那么它不能够被连接。我们可以通过设置pthread_create()中的attr参数来设置创建线程时的这个属性,对attr的使用流程如下。原创 2023-11-05 17:10:53 · 199 阅读 · 0 评论 -
5.2 向线程传递参数
pthread_create()允许编程人员向线程的执行方法中传入一个参数,对于需要传递多个参数的情况,可以将这些参数封装到一个结构体中,然后将结构体对象的指针作为参数进行传入。这个例程中进行了错误的参数传递,它把所有线程都能访问的共享内存中的变量t的地址传递给了各个线程。随着循环的进行,t的值可能会发生预料外的改变,从而对使用它的线程产生影响。主线程中每个子线程使用独有的一份数据内存进行传输,确保每个线程参数在传递过程中互不干扰。问题:考虑到线程启动和调度的不确定性,如何将参数安全地传递给创建的线程?原创 2023-11-05 17:07:24 · 203 阅读 · 0 评论 -
5.1 创建和销毁线程
同时也可以设置线程的调度优先级。下面的例子展示了如何在Linux中获取和设置有关线程数限制的信息,首先我们获取了当前默认的设置,然后将进程的最大数量(包含线程)设为最大值,最后我们验证了修改生效。如果在main()中没有显式调用pthread_exit(),可能会出现主线程比它创建的线程更早结束的情况,此时所有的线程都会被强制结束,从而产生一些超出预期的情况。在pthread_exit()方法中编程人员可以传入一个可选的有关终止状态的参数,这个参数会传递给连接了被终止线程的其他线程(后续章节内容中阐述)。原创 2023-11-02 20:55:35 · 313 阅读 · 0 评论 -
4.编译多线程应用程序
Linux中pthread作为一个单独的库存在(libpthread.so),而在其他Unix系统中却不一定,在这些系统中使用-lpthread是无法实现链接到对应的pthread库的作用的;而-pthread选项除了会链接pthread库,还会增加一些影响编译的宏的定义,使得一些老版本基础库中的接口是线程安全的。gcc下编译时我们会碰到两个选项-pthread和-lpthread,记住推荐使用-pthread而不是-lpthread,原因如下。在不同平台下使用多线程的程序编译时的编译选项设置如下图所示。原创 2023-10-30 17:30:30 · 291 阅读 · 0 评论 -
3. Pthreads API
目前的POSIX标准只支持C语言,Fortran编程人员可以使用封装了相关C实现的接口进行编程,有些Fortran编译器可能提供了一个Fortran版本的Pthreads API。对互斥量进行创建、销毁、加锁、解锁操作的函数,还有对其进行设置/获取属性的函数。对象隐藏不透明的设计概念贯穿整个API——基础接口用于创建/修改相关对象,同时使用相关接口来修改对象的相关属性。包括创建、销毁、等待、发出信号等操作,和设置/获取属性的相关函数。线程管理:线程的创建、分离、连接等,还有设置/获取线程的相关属性等函数。原创 2023-10-17 20:56:14 · 145 阅读 · 0 评论 -
2.4 设计多线程的程序
譬如允许的线程最大数量,和线程的默认堆栈大小,这两个重要信息可能在不同平台上有差异,而它们又是设计程序中需要特别注意的信息。如果两个任务在时间轴上是可交换的,互相交错的,那么它们可以用各自的线程来执行,如下图所示。所有的线程可以访问全局共享内存,同时它们也有自己的私有数据。对于编程人员来说,使用第三方库的影响在于如果你无法100%确定某个操作是线程安全的,那么有可能会带来相关的问题。流水线(pipeline):一个任务被分解为多个子操作,每个子操作被单独的线程执行,汽车流水线的生产模式和其十分相像。原创 2023-10-13 15:20:33 · 116 阅读 · 0 评论 -
2.3 为何使用Pthreads
和进程相比,系统在创建和管理线程时的开销要小的多,这里通过比较调用fork()创建进程和pthread_create()创建线程时的耗时开销来说明。在支持高性能计算的平台上使用Pthreads可以实现性能的最优化,特别是当一个应用使用MPI进行进程间通信时,此时使用Pthreads代替MPI一般可以获得更高的性能。最坏的情况下,使用Pthreads编程时可能会遇到缓存到CPU或者内存到CPU的带宽问题,但整体来说其速度仍然要比通过共享内存通信的MPI要快不少。原创 2023-10-10 20:27:17 · 146 阅读 · 0 评论 -
2.2 Pthreads是什么
以前硬件供应商一般都会提供相应硬件平台专用的线程库,使得多线程编程的代码的可移植性成为另开发者头疼的一个问题。为了更好地利用线程进行相关开发,制定标准的线程编程接口迫在眉睫,由此Pthreads诞生了。Pthreads使用C语言实现,包括一个pthread.h头文件和一个库文件;有时候它不一定是独立的库文件,而是被包含在其他库文件之中,譬如libc。POSIX标准,包括Pthreads规范目前也在持续更新中。原创 2023-10-10 20:15:27 · 164 阅读 · 0 评论 -
2.1 线程是什么
对于开发者,独立于主程序运行的一个“程序”可以称为一个线程。更进一步来说,如果一个应用包含多个“程序”,这些“程序”能够被操作系统同时/独立的运行,我们会称它为多线程的应用。线程是属于进程的一种资源,且使用进程的其他资源,能够被操作系统调度作为独立个体运行。为了完成上述功能,线程只持有一些能够使其正常运行的必要资源,包括。在理解线程的概念之前,我们需要先理解UNIX中的进程。技术上来说,线程是操作系统能够操作运行的一组独立的指令运算流程;更具体地来说,它的意义如下。下图是拥有线程的进程所持有的资源示意图。原创 2023-10-10 19:52:57 · 118 阅读 · 0 评论