线程同步
同步,顾名思义就是齐步并进。异步,就是两个人各走各的不管对方。两个同步的线程要时不时停下来等等对方,比如说一个线程负责读磁盘、另一个线程负责解析文件内容,那么解析文件内容的线程就要等都读磁盘的线程把内容读出来才能解析。而异步的两个线程各自干自己的工作,不会因为对方状况而阻塞。
在实际工作中总是会需要一些并发的操作,比如最基本的,服务器总要同时维持很多连接吧。
比如你要记录当前的连接数,那么就要有一个全局的cnt,来一个连接就cnt++,断一个连接就cnt–。
但是在汇编层面cnt++这个操作可能长这样(我这里写的是mips):
lw $t1, 8($t0)
addi $t1, $t1, 1
sw $ t1, 8($t0)
所以到机器层面上一共要执行三条指令,万一执行到一半的时候因为什么中断切出去了(比如时钟中断之后直接进行了线程切换),那么这个被取出的cnt值就会随着换出的上下文被存起来,这个时候如果另一个指令又执行了cnt++,然后再切回来的时候,+1操作就成了基于原先的cnt而不是最新的cnt。所以最后cnt的值就比实际的要小。
解决这个问题的方法就是引入一些同步机制。比如互斥锁mutex,或者信号量semaphore。这些东西最终在机器层面上都是有支持的,比如可以设计一种不会被打断的一种原子操作,所谓原子操作就是不会切换上下文,处理器可以执行一条关中断的指令来暂时屏蔽中断来达成这种效果。
具体在硬件层面的东西我不是很熟悉,我只是个程序员,今天我想从软件层面上探索一下C++的stl库里实现的一些同步机制。
我用这个网站来测试我的程序:https://leetcode-cn.com/problems/print-in-order/submissions/
mutex
mutex这个词是从mutal exclusive造出来的,意思是互斥。假设有好几个线程尝试对其上锁,那么第一个上锁操作会成功,后面对其上锁的线程会被阻塞。当该锁被解锁的时候,被阻塞的线程的其中一个会被唤醒,并且该锁再次被上锁。所以同一时间只能由某一个线程拥有该锁,如果你把锁放在互斥代码段的开头和结尾,就相当同时只有一个线程能够执行互斥代码。
构造函数是无参的,然后lock函数是阻塞式的,try_lock是非阻塞式的,非阻塞式的方法返回一个bool值表示加锁是否成功。unclock就是解锁,文档说如果对未上锁的锁进行unlock是未定义的行为。
以下代码是在leetcode1114上可以通过测试的,这份代码依次打印first
,second
,third
:
class Foo {
public:
std::mutex m2, m3;
Foo() {
m2.lock();
m3.lock();
}
void first(function<void()> printFirst) {
// printFirst() outputs "first". Do not change or remove this line.
printFirst();
m2.unlock();
}
void second(function<void()> printSecond) {
// printSecond() outputs "second". Do not change or remove this line.
m2.lock();