一 什么是线程安全
通俗的说,如果在多线程下,每一个线程都能正常的工作,最终产生的结果也是确定的,那么这就是线程安全的。
常见的保证线程安全的手段
常见的有两个保证线程安全的手段,一个是加锁,另一个是采用原子操作(在某些特定情况下)。
一个线程不安全的实例
int cnt(0);
void increase(int time) {
for (int i = 0; i < time; i++) {
cnt++;
}
}
void decrease(int time) {
for (int i = 0; i < time; i++) {
cnt--;
}
}
int main(int argc, char** argv) {
std::thread t1(increase, 1000000);
std::thread t2(decrease, 1000000);
t1.join();
t2.join();
std::cout << "counter:" << cnt << std::endl;
return 0;
}
定义一个全局变量cnt,启用两个线程,分别对全局变量进行加操作和减操作,最终结果为:
counter:-637175
但是结果应该是0才对。
方法一:采用锁
在每次自增或者自减操作之前上锁,操作完成或放锁。
int cnt(0);
mutex mtx;
void increase(int time) {
for (int i = 0; i < time; i++) {
mtx.lock();
cnt++;
mtx.unlock();
}
}
void decrease(int time) {
for (int i = 0; i < time; i++) {
mtx.lock();
cnt--;
mtx.unlock();
}
}
int main(int argc, char** argv) {
std::thread t1(increase, 1000000);
std::thread t2(decrease, 1000000);
t1.join();
t2.join();
std::cout << "counter:" << cnt << std::endl;
return 0;
}
结果:counter:0
执行操作明显延长了很多,加锁有一定的开销。
方法二:采用原子操作
原子操作为c++11新增特性,代码如下:
#include <atomic>
atomic<int> cnt(0);
void increase(int time) {
for (int i = 0; i < time; i++) {
cnt++;
}
}
void decrease(int time) {
for (int i = 0; i < time; i++) {
cnt--;
}
}
int main(int argc, char** argv) {
std::thread t1(increase, 1000000);
std::thread t2(decrease, 1000000);
t1.join();
t2.join();
std::cout << "counter:" << cnt << std::endl;
return 0;
}
counter:0
执行速度很快,原子操作内部是采用了cas操作,消耗较小(compare and set)。其中
a++
中的++为atomic的重载运算符,如果写成a=a+1,则失去原子性,结果不正确。同时,以下这些运算符都具有原子性: