基于线程的并发编程
进程与线程的区别
一、进程是资源分配的最小单位,线程是CPU调度的最小单位。
线程是运行在进程上下文中的逻辑流,所有运行在同一个进程里的线程共享该进程的虚拟地址空间,包括代码、数据、堆、共享库和打开的文件列表。
线程上下文,包括唯一的整数线程ID(TID)、栈、栈指针、程序计数器、通用目的寄存器和条件码。
二、系统开销
一个线程的上下文要比进程的小得多,切换起来也更快。
在创建或撤消进程时,系统都要为之分配或回收资源,如内存空间、IO设备等。因此,操作系统所付出的开销将显著地大于在创建或撤消线程时的开销。
类似地,在进行进程切换时,涉及到整个当前进程CPU环境的保存以及新被调度运行的进程的CPU环境的设置。
而线程切换只须保存和设置少量寄存器的内容,并不涉及存储器管理方面的操作。可见,进程切换的开销也远大于线程切换的开销。
三、组织结构
和一个进程相关的线程组成一个对等线程池,独立于其他线程创建的线程。线程不像进程那样,按照严格的父子层次来组织。
信号量
由Edsger Dijkstra提出一种经典的解决同步不同线程问题的方法,叫做信号量(semaphore)的特殊类型变量。
Posix标准定义了许多操作信号量的函数。
#include <semaphore.h>
int sem_init(sem_t *sem, 0, unsigned int value);
int sem_wait(sem_t *s); /* P(s) */
int sem_post(sem_t *s); /* V(s) */
将mutex初始化为1:
sem_init(&mutex, 0, 1); /* mutex = 1 */
LeetCode 1114. 按序打印
我们提供了一个类:
public class Foo {
public void first() { print("first"); }
public void second() { print("second"); }
public void third() { print("third"); }
}
三个不同的线程 A、B、C 将会共用一个 Foo 实例。
一个将会调用 first() 方法
一个将会调用 second() 方法
还有一个将会调用 third() 方法
请设计修改程序,以确保 second() 方法在 first() 方法之后被执行,third() 方法在 second() 方法之后被执行。
示例 1:
输入: [1,2,3]
输出: "firstsecondthird"
解释:
有三个线程会被异步启动。
输入 [1,2,3] 表示线程 A 将会调用 first() 方法,线程 B 将会调用 second() 方法,线程 C 将会调用 third() 方法。
正确的输出是 "firstsecondthird"。
示例 2:
输入: [1,3,2]
输出: "firstsecondthird"
解释:
输入 [1,3,2] 表示线程 A 将会调用 first() 方法,线程 B 将会调用 third() 方法,线程 C 将会调用 second() 方法。
正确的输出是 "firstsecondthird"。
#include <semaphore.h>
class Foo {
public:
Foo() {
sem_init(&mutex_1, 0, 0);
sem_init(&mutex_2, 0, 0);
}
void first(function<void()> printFirst) {
// printFirst() outputs "first". Do not change or remove this line.
printFirst();
sem_post(&mutex_1);
}
void second(function<void()> printSecond) {
// printSecond() outputs "second". Do not change or remove this line.
sem_wait(&mutex_1);
printSecond();
sem_post(&mutex_2);
}
void third(function<void()> printThird) {
// printThird() outputs "third". Do not change or remove this line.
sem_wait(&mutex_2);
printThird();
}
private:
sem_t mutex_1;
sem_t mutex_2;
};