资源
一个可供线程访问的变量、设备或内存块等类型的实体被称为资源
。 可供多个线程访问的资源被称为共享资源
;
并发竞争
并发
是指多个执行单元同时、并发执行。而并发的执行单元对共享资源的访问被称为共享资源竞争
。
如果在访问共享资源时不独占该共享资源,可能会造成资源异常(如变量值混乱、设备出错或内存块内容不是期望值等),进而导致程序运行异常甚至崩溃。
SylixOS支持SMP与内核抢占,在此环境下更是充满了并发与竞争。
在SylixOS中,可能导致并发与竞争的情况有:
- 对称多处理器(SMP)。SMP是一种紧耦合、共享存储的系统模型,特点是多个CPU可以使用共同的系统总线,因此可访问共同的外设和存储器;
- 内核进程的抢占。SylixOS是可抢占的,所以一个内核进程可能被另一个高优先级的内核进程抢占。如果两个进程共同访问共享资源,就会出现竞争。
- 中断。中断可打断正在执行的进程,若中断处理程序访问进程正在访问的资源,则也会引发竞争。中断也可能被新的更高优先级的中断打断,因此,多个中断之间也可能引起并发而导致竞争。
以上三种情况只有SMP是真正意义上的并行,而其他都是宏观上的并行,微观上的串行,但是都会引发对共享资源的竞争问题。
举例:
现在有两个线程(线程A和线程B)需要同时将同一个变量V(初始值为0)进行加一操作。
在RISC机器上,一般都是load/store体系结构,即访问内存只允许load和store操作; 变量V自增操作的机器指令流程如下:
(1) 加载变量V的地址到CPU的工作寄存器0;
(2) load指令将工作寄存器0存储的地址里的内容加载工作寄存器1;
(3)inc指令将工作寄存器1的值加一;
(4) store指令将工作寄存器1的值保存到工作寄存器0指向的地址。
由上看到,变量V的自增操作不是一步完成的,如果线程A和线程B依次完成以上四步,那么最后变量V的值将会是2。
如果线程A完成了前面三步,这时线程B打断了线程A的工作,线程B将变量V改写为1;随后线程A继续执行第四步,那么最后变量V的值仍然是1,这显然不是我们期望的。
互斥访问
为了解决这种问题,我们需要对该过程进行互斥访问,互斥是一种排它性行为,也即同一时间只允许一个线程访问共享资源。实现互斥有多种方法:关中断、禁止任务调度、信号量等。
针对上面的过程,我们可以加入一把锁(信号量),在进行变量V的自增操作前必须占有该锁,操作完成后释放该锁;假设锁已经被线程A占有,如果线程B也要申请该锁,因为锁具有排它性,线程B将被阻塞;这样确保了同一时间只有一个线程能访问该变量,这样变量V的值就不会有混乱的风险。
我们称被锁保护的区域为临界区
。 如果临界区保护的代码不可被打断,那么过程是原子性的操作,不可打断意味着临界区内不存在阻塞和硬件中断发生,原子性操作屏蔽了当前CPU核心的硬件中断响应,所以原子性操作应该尽量简短。
在多线程环境下,每一个变量的操作都需要小心对待。多线程编程可以使我们的程序清晰和易于实现,但需要我们谨慎地设计。SylixOS为我们准备了大量解决多线程编程互斥问题的解决方案,例如信号量、互斥锁、消息队列等。