多线程下int自增线程安全问题

2020-09-20  本文已影响0人  飞翃荷兰人

一 什么是线程安全

通俗的说,如果在多线程下,每一个线程都能正常的工作,最终产生的结果也是确定的,那么这就是线程安全的。

常见的保证线程安全的手段

常见的有两个保证线程安全的手段,一个是加锁,另一个是采用原子操作(在某些特定情况下)。

一个线程不安全的实例
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,则失去原子性,结果不正确。同时,以下这些运算符都具有原子性:


image.png
上一篇下一篇

猜你喜欢

热点阅读