锁:mutex
锁的本质属性是为事物提供“访问保护”
在c++等高级编程语言中,锁也是用来提供“访问保护”的,被保护的是内存中的各种变量。此外,计算机领域对于“锁”有个响亮的名字——mutex(互斥量)
Mutex,互斥量,就是互斥访问的量。这种东东只在多线程编程中起作用,在单线程程序中是没有什么用处的。从c++11开始,c++提供了std::mutex
类型,对于多线程的加锁操作提供了很好的支持。下面看一个简单的例子,对于mutex形成一个直观的认识。
无锁的情况:mutex_no_mutex.cpp
假定有一个全局变量counter,启动两个线程,每个都对该变量自增10000次,最后输出该变量的值。
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
#include <chrono>
#include <stdexcept>
int counter = 0;
void increase(int time) {
for (int i = 0; i < time; i++) {
// 当前线程休眠1毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(1));
counter++;
}
}
int main(int argc, char** argv) {
std::thread t1(increase, 10000);
std::thread t2(increase, 10000);
t1.join();
t2.join();
std::cout << "counter:" << counter << std::endl;
return 0;
}
为了显示多线程竞争导致结果不正确的现象,在每次自增操作的时候都让当前线程休眠1毫秒
如果没有多线程编程的相关经验,我们可能想当然的认为最后的counter为20000,如果这样想的话,那就大错特错了。下面是两次实际运行的结果:
[root@2d129aac5cc5 demo]# ./mutex_no_mutex
counter:19997
[root@2d129aac5cc5 demo]# ./mutex_no_mutex
counter:19996
出现上述情况的原因是:自增操作"counter++"不是原子操作,而是由多条汇编指令完成的。多个线程对同一个变量进行读写操作就会出现不可预期的操作。以上面的demo1作为例子:假定counter当前值为10,线程1读取到了10,线程2也读取到了10,分别执行自增操作,线程1和线程2分别将自增的结果写回counter,不管写入的顺序如何,counter都会是11,但是线程1和线程2分别执行了一次自增操作,我们期望的结果是12!!!!!
轮到mutex上场。
定义一个std::mutex
对象用于保护counter变量。对于任意一个线程,如果想访问counter,首先要进行"加锁"操作,如果加锁成功,则进行counter的读写,读写操作完成后释放锁(重要!!!); 如果“加锁”不成功,则线程阻塞,直到加锁成功。
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
#include <chrono>
#include <stdexcept>
int counter = 0;
std::mutex mtx; // 保护counter
void increase(int time) {
for (int i = 0; i < time; i++) {
mtx.lock();
// 当前线程休眠1毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(1));
counter++;
mtx.unlock();
}
}
int main(int argc, char** argv) {
std::thread t1(increase, 10000);
std::thread t2(increase, 10000);
t1.join();
t2.join();
std::cout << "counter:" << counter << std::endl;
return 0;
}
先来看几次运行结果:
[root@2d129aac5cc5 demo]# ./mutex_with_mutex
counter:20000
[root@2d129aac5cc5 demo]# ./mutex_with_mutex
counter:20000
[root@2d129aac5cc5 demo]# ./mutex_with_mutex
counter:20000
这次运行结果和我们预想的一致,原因就是“利用锁来保护共享变量”,在这里共享变量就是counter(多个线程都能对其进行访问,所以就是共享变量啦)。
简单总结一些std::mutex
:
- 对于
std::mutex
对象,任意时刻最多允许一个线程对其进行上锁; mtx.lock()
:调用该函数的线程尝试加锁。如果上锁不成功,即:其它线程已经上锁且未释放,则当前线程block。如果上锁成功,则执行后面的操作,操作完成后要调用mtx.unlock()
释放锁,否则会导致死锁的产生;mtx.unlock()
:释放锁;std::mutex
还有一个操作:mtx.try_lock()
,字面意思就是:“尝试上锁”,与mtx.lock()
的不同点在于:如果上锁不成功,当前线程不阻塞。