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

C++互斥量mutex和原子变量atomic,第1张

1.互斥量mutex: 1.1 多线程的问题:

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


#include 
#include 
#include 
#include 

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


它可以使用lock函数锁住互斥量。


首先看下列代码:

#include 
#include 
#include 
#include 

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 
#include 
#include 
#include 

std::mutex mtx;

void func1(int& a)
{
    for (int i = 0; i < 100*1000; i++) {
        std::lock_guard 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 lck(mtx);
        a+=1;
    }
}

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


2.原子变量atomic: 2.1 原子 *** 作:

        原子 *** 作表示在多个线程访问同一个全局资源的时候,能够确保所有其他线程都不在同一时间访问该资源。


也就是确保了在同一时刻只有一个线程对这个资源进行访问。


虽然上文的互斥对象也可以共享资源的访问的保护,但是原子 *** 作更加接近底层,因而效率一般比互斥对象更高。


2.2 原子变量的使用:

        原子变量需要c++11以上版本并包含头文件#include。


使用如下:

#include 
#include 
#include 
#include 

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

int main()
{
    std::atomic 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');

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


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/563223.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-02
下一篇 2022-04-02

发表评论

登录后才能评论

评论列表(0条)

保存