同步器
Linux 并发与通信
并发控制中主要考虑线程之间的通信(线程之间以何种机制来交换信息)与同步(读写等待,竞态条件等)模型,在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递。Java 就是典型的共享内存模式的通信机制;而 Go 则是提倡以消息传递方式实现内存共享,而非通过共享来实现通信。
在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信。在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信。同步是指程序用于控制不同线程之间操作发生相对顺序的机制。在共享内存并发模型里,同步是显式进行的。程序员必须显式指定某个方法或某段代码需要在线程之间互斥执行。在消息传递的并发模型里,由于消息的发送必须在消息的接收之前,因此同步是隐式进行的。
进程/线程间的通信
操作系统的主要任务是管理计算机的软件、硬件资源。现代操作系统的主要特点是多用户和多任务,也就是程序的并行执行。所以操作系统就借助于进程来管理计算机的软、硬件资源,支持多任务的并行执行。要并行执行就需要多进程、多线程。因此多进程和多线程间为了完成一定的任务,就需要进行一定的通信。而线程间通信又和进程间的通信不同。由于进程的数据空间相对独立而线程是共享数据空间的,彼此通信机制也很不同。
Linux 中的进程,是有 fork()
系统调用创建的,进程间都有独立的地址空间,他们之间不能直接通信,必须通过一些 IPC 进程进程间通信机制来完成。常见的 IPC 有:管道,命名管道,信号,信号量,共享内存以及 socket 等。Linux 中的线程,是 clone()
系统调用创建的,一个进程下的线程间是共享内存空间的,故线程 A 可以之间访问线程 B 中定义的变量。比起进程复杂的通信机制(管道、匿名管道、消息队列、信号量、共享内存、内存映射以及 socket 等),线程间通信要简单的多。
因为同一进程的不同线程共享同一份全局内存区域,其中包括初始化数据段、未初始化数据段,以及堆内存段,所以线程之间可以方便、快速地共享信息。只需要将数据复制到共享(全局或堆)变量中即可。不过,要避免出现多个线程试图同时修改同一份信息。线程中常用的通信机制包括了锁、信号量与信号:
-
锁:包括互斥锁、条件变量、读写锁;互斥锁提供了以排他方式防止数据结构被并发修改的方法。使用条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的,条件变量始终与互斥锁一起使用。读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
-
无名信号量(Semaphore):无名信号量只用于线程间的同步,有名信号量只用于进程间通信。信号量是属于 POSIX:SEM 的,不是属于 POSIX:THR 的,需要的文件头是。两者的共同点都是相当于计数器,用于限制多个进程对有限共享资源的访问。
-
信号(Signal):线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。