C 程序中经常同时执行多项任务。例如,一个程序可能:
(1) 在执行程序过程中通过完成并行任务来提高性能。
(2) 在处理用户输入的同时,在后台进行耗时的数据通信和实时操作。
通过并行执行(concurrent execution)程序中的部分代码,可以实现不同任务同时进行。特别是在多处理器系统(当然也包括多核处理器)上,程序通过并行机制更有效地使用系统资源,其意义越来越重大。
C11 标准以前,C 开发人员必须依赖操作系统或相应链接库来实现并行。C11 标准发布以后,使得 C 程序可便捷地实现并行。C11 支持多线程执行(multithreaded execution)。
多线程指的是在一个程序进程中处理控制流的多路并行通道,它在所有操作系统上为运行该程序提供了相同程度的并发性。为此,C11 标准定义了一个相应的内存模型(memory model),并且支持原子操作(atomic operation)。
小编分类整理了许多C语言进阶学习材料和BAT面试题,需要资料的请进Q群:731611386免费领取
C语言线程和进程
当启动一个程序时,操作系统创建一个进程,并在该进程中执行程序。一个进程包括一个或多个线程。每个线程又是一个局部进程,它以独立于其他局部进程的方式执行一个命令序列。
当进程启动时,它的主线程则成为活动线程。这时,任何正在运行的线程都可以启动其他线程。当进程终止时,例如,通过在 main()函数中执行一个 return 语句或通过调用 exit()函数,所有已开启但还未结束的线程都会被终止。
系统调度器为所有可运行的线程平均分配可用的 CPU 时间。通常,调度器是抢占式的:它会中断正在执行的线程,给中央处理单元(CPU)留出可用的短暂时间,并将 CPU 分配给其他线程使用一段时间。
这种调度的结果是:即使是在单处理系统上,在用户面前运行的线程看上去像是在同时执行,实际上,只有在多处理器系统中,几个线程才可能真正地同时执行。
每一个进程在内存中都有自己的地址空间,并拥有独占的资源,例如,打开的文件。一个进程中的所有线程都继承该进程的资源。最具有意义的是,在一个进程中的几个线程共享一个地址空间。这使得在一个进程中的任务切换比在不同进程间的任务切换要简单得多。
然而,为了在不同线程间切换任务,每个线程也拥有自己的资源:包括栈存储器和 CPU 寄存器。这些资源允许每个线程在不受其他线程干扰的条件下,处理自身的本地数据。此外,一个线程也可以具有线程专用的永久内存。
对于一个给定进程,由于它内部的所有线程均使用相同的地址空间,所以它们共享全局数据与静态数据。然而,这也意味着,同一个进程中的两个不同线程可以同时访问同一个内存单元。这种情况在 C 标准中被称为数据竞争(data race),或者通常称之为竞态条件(race condition)。
为了防止在共享数据时出现冲突,当这些不同线程使用内存中相同位置时,程序员必须明确地同步这些不同线程的写操作或读写操作。
C语言创建线程thread_create()
在头文件 threads.h 中,定义和声明了支持多线程的宏、类型和函数。所有直接与线程相关的标识符,均以前缀 thrd_ 作为开头。例如,thrd_t 是一个对象类型,它标识了一个线程。
函数 thrd_create()用于创建并开始执行一个新线程。函数 thrd_create()的其中一个参数为在新线程中需要被执行的函数 thrd_create()的其中一个参数为在新线程中需要被执行的函数。thrd_create()的完整原型是:
intthrd_create(thrd_t thr,thrd_start_t func,voidarg);
参数 func 是一个指针,它指向在新线程需要被执行的函数,而 void 指针 arg 用于向该函数传递参数。换句话说,新线程将执行函数调用 func(arg)。
参数 func 的类型为 thrd_start_t,它被定义为 int()(void)(这是一个函数指针,指向一个 void 指针作为其参数并返回一个 int 值的函数),因此,该线程执行的函数返回一个 int 类型的值。
程序在后续过程中可以通过调用函数 thread_join()获得这个 int 类型的返回值(必要时,需等待该线程执行完)。
如果一个线程启动成功,函数 thread_create()将新线程写入一个对象进行标识,并通过参数 thr 指向该对象,然后返回宏值 thread_success。
在大多数情况下,后续的其他操作均依赖于该线程的执行结果,并且只有当该线程完成后,才能执行其他操作。函数 thread_join()用于确保一个线程已完成。它的原型是:
intthrd_join(thrd_t thr,int*result);
调用 thread_join()的线程会被阻塞,直到通过 thr 标识的线程执行完成,这里“阻塞”(block)指的是:线程会在调用 thread_join()的位置停留必要的时间。然后,thread_join()将线程 thr 中执行函数的返回值写入指针 result 所引用的 int 变量中,假设 result 不是一个空指针。最后,thread_join()释放属于线程 thr 的所有资源。
如果程序逻辑上并不需要等待线程 thr 结束,则应该调用以下函数:
int thrd_detach(thrd_t thr);
thrd_detach()使得当线程 thr 执行完成后,自动释放线程占用的所有资源。一旦一个线程执行了分离操作(调用 thrd_detach()),将不用程序等待其结束,程序也不会获得该线程执行函数的返回值。对于每个创建的线程,调用 thread_join()或 thread_detach()不得超过一次。
在例 1 中的程序展示了使用并行操作处理数组的一种方式。各个线程先自行处理数组的各部分,然后将它们的处理结果组合在一起。该程序仅需计算一个数字序列的总和。
函数 sum()首先根据创建线程的最大数量确定划分数组所得的各组元素的最大数量,然后调用递归辅助函数 parallel_sum()。
函数 parallel_sum()将数组平均分为两半,将其中的一半交给一个新线程处理,同时调用自身来处理另一半数组。如该例所示,一个线程函数需要多个参数,这些参数通常采用结构进行封装。
小编分类整理了许多C语言进阶学习材料和BAT面试题,需要资料的请进Q群:731611386免费领取