C++互斥量mutex和原子变量atomic

1.互斥量mutex:

1.1 多线程的问题:

        当使用多线程编程时,如果多个线程同时访问同一资源很可能会出现错误,下面程序展示了这点。

#include <thread>
#include <mutex>
#include <atomic>
#include <iostream>

void func1(int& a)
{
    for (int i = 0; i < 100*1000; i++) {
        a+=1;
    }
}

int main()
{
    int a = 0;
    std::thread t1(func1,std::ref(a));
    std::thread t2(func1, std::ref(a));
    t2.join();
    t1.join();
    std::cout << a<<'\n';
    a = 0;
    t1=std::thread(func1, std::ref(a));
    t2=std::thread(func1, std::ref(a));
    t2.join();
    t1.join();
    std::cout << a;
    return 0;
}

        上述程序我们开启两个线程,每个线程对a进行10万次+1,理论上最终应该是20万,然而运行后结果如下:

        显然,结果并不是20万,而且两次对a进行双线程操作后结果也不一样,这就是产生的问题。因此我们需要一种方式让资源同时仅会被访问一次,于是我们引入了互斥锁mutex。

1.2 互斥锁mutex的使用:

        互斥锁mutex是c++11及更高版本所有的,需要包含头文件#include <mutex>。它可以使用lock函数锁住互斥量。首先看下列代码:

#include <thread>
#include <mutex>
#include <atomic>
#include <iostream>

std::mutex mtx;

void func1(int& a)
{
    for (int i = 0; i < 100*1000; i++) {
        mtx.lock();
        a+=1;
        mtx.unlock();
    }
}

int main()
{
    int a = 0;
    std::thread t1(func1,std::ref(a));
    std::thread t2(func1, std::ref(a));
    t2.join();
    t1.join();
    std::cout << a<<'\n';
    a = 0;
    t1=std::thread(func1, std::ref(a));
    t2=std::thread(func1, std::ref(a));
    t2.join();
    t1.join();
    std::cout << a;
    return 0;
}

        下面是运行结果 

         可以发现成功输出了正确的值。

        互斥锁使用lock可以锁住互斥量,然后此时如果互斥量mtx被其它线程锁住那么将阻塞直到该互斥量被unlock。将互斥量上锁之后一定要使用unlock(),否则会造成资源被锁住,进而有可能产生死锁。

1.3 lock_guard:

        由于互斥锁上锁需要解锁,然而有时写代码时会忘记解锁。此外就算足够严谨,在代码里给出了解锁,也有可能会因程序异常终止而无法释放锁,例如下面伪代码:

void func()
{
    mtx.lock();
    otherFunc();
    mtx.unlock();
}

         如果函数otherFunc出现了异常或者触发了某种设置导致程序或者该函数直接终止,那么将无法调用unlock()。因此能够自动释放锁的方式是必要的。我们可以想到写一个类,然后在析构时释放锁。lock_guard就有如下效果。使用了lock_guard的程序如下:

#include <thread>
#include <mutex>
#include <atomic>
#include <iostream>

std::mutex mtx;

void func1(int& a)
{
    for (int i = 0; i < 100*1000; i++) {
        std::lock_guard<std::mutex> lck(mtx);
        a+=1;
    }
}

int main()
{
    int a = 0;
    std::thread t1(func1,std::ref(a));
    std::thread t2(func1, std::ref(a));
    t2.join();
    t1.join();
    std::cout << a<<'\n';
    a = 0;
    t1=std::thread(func1, std::ref(a));
    t2=std::thread(func1, std::ref(a));
    t2.join();
    t1.join();
    std::cout << a;
    return 0;
}

        结果仍然是正确的。

1.4 unique_lock

        unique_lock也可以自动释放锁。和lock_guard相比unique_lock有更多功能,最常见的就是可以和条件变量共同使用。不过功能多付出的代价是效率和lock_guard相比会低一些。因此如果只是需要加锁和自动释放锁,则只使用lock_guard会更好一些,下面是unique_lock的使用方法:

void func1(int& a)
{
    for (int i = 0; i < 100*1000; i++) {
        std::unique_lock<std::mutex> lck(mtx);
        a+=1;
    }
}

        在简单的加锁上,unique_lock和lock_guard使用方式几乎一样。

2.原子变量atomic:

2.1 原子操作:

        原子操作表示在多个线程访问同一个全局资源的时候,能够确保所有其他线程都不在同一时间访问该资源。也就是确保了在同一时刻只有一个线程对这个资源进行访问。虽然上文的互斥对象也可以共享资源的访问的保护,但是原子操作更加接近底层,因而效率一般比互斥对象更高。

2.2 原子变量的使用:

        原子变量需要c++11以上版本并包含头文件#include<atomic>。使用如下:

#include <thread>
#include <mutex>
#include <atomic>
#include <iostream>

//原子变量和int不是一种类型,因此需要使用std::atomic<int>修饰参数
void func1(std::atomic<int>& a)
{
    for (int i = 0; i < 100*1000; i++) {
        a+=1;
    }
}

int main()
{
    std::atomic<int> a(0);//定义原子变量
    std::thread t1(func1,std::ref(a));
    std::thread t2(func1, std::ref(a));
    t2.join();
    t1.join();
    std::cout << a<<'\n';
    a = 0;
    t1=std::thread(func1, std::ref(a));
    t2=std::thread(func1, std::ref(a));
    t2.join();
    t1.join();
    std::cout << a;
    return 0;
}

        这样无需加锁也能得到正确的结果。

       对于基本类型还可以使用预先定义的别名,如

std::atomic_int a(0);
std::atomic_char b('0');

        原子变量的效率比互斥量要高很多。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值