C++11互斥量mutex使用详解

这篇具有很好参考价值的文章主要介绍了C++11互斥量mutex使用详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、前言

mutex又称互斥量,C++ 11中与 mutex相关的类(包括锁类型)和函数都声明在#include头文件中,所以如果你需要使用 std::mutex,就必须包含#include头文件。
C++11提供如下4种语义的互斥量(mutex) :
std::mutex,独占的互斥量,不能递归使用。
std::time_mutex,带超时的独占互斥量,不能递归使用。
std::recursive_mutex,递归互斥量,不带超时功能。
std::recursive_timed_mutex,带超时的递归互斥量。

二、独占互斥量std::mutex

std::mutex 是C++11 中最基本的互斥量,std::mutex 对象提供了独占所有权的特性,即不支持递归地对 std::mutex 对象上锁,而 std::recursive_lock 则可以递归地对互斥量对象上锁。

2.1、std::mutex 的成员函数

  1. 构造函数,std::mutex不允许拷贝构造,也不允许 move 拷贝,最初产生的 mutex 对象是处于 unlocked 状态的。
  2. lock(),调用线程将锁住该互斥量。线程调用该函数会发生下面 3 种情况:(1). 如果该互斥量当前没 有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,该线程一直拥有该锁。(2). 如果当 前互斥量被其他线程锁住,则当前的调用线程被阻塞住。(3). 如果当前互斥量被当前调用线程锁 住,则会产生死锁(deadlock)。
  3. unlock(), 解锁,释放对互斥量的所有权。
  4. try_lock(),尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻塞。线程调用该 函数也会出现下面 3 种情况,(1). 如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用 unlock 释放互斥量。(2). 如果当前互斥量被其他线程锁住,则当前调用线程返回 false,而并不会被阻塞掉。(3). 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。

2.2、使用示例

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

#define TRY_MUTEX       0
#define MY_MUTEX        1

volatile int counter(0); // non-atomic counter

std::mutex mtx;

void increases10k()
{
        for(int i=0;i<10000;i++)
        {
#if TRY_MUTEX
                if(mtx.try_lock())
                {
                        ++counter;
                        mtx.unlock();
                }
#elif MY_MUTEX
                mtx.lock();
                ++counter;
                mtx.unlock();
#endif
        }

}

int main(int argc,char **argv)
{
        std::thread threads[10];
        for(int i=0;i<10;i++)
        {
                threads[i]=std::thread(increases10k);
        }
        for(auto& th:threads)
                th.join();
        std::cout << " successful increases of the counter " << counter <<std::endl;
        return 0;

}

编译:

 g++ -o mutex_test mutex_test.c -std=c++11 -lpthread

执行效果:

# try_lock
$ ./mutex_test 
 successful increases of the counter 53612
# lock
$ vim mutex_test.c
 successful increases of the counter 100000

可以看到try_lock只是尝试加锁,不管是否成功都不阻塞,而lock如果加锁失败会一直阻塞直到加锁成功。

三、lock_guard和unique_lock的使用和区别

相对于手动lock和unlock,可以使用RAII(通过类的构造析构)来实现更好的编码方式。 RAII:也称为“资源获取就是初始化”,是c++等编程语言常用的管理资源、避免内存泄露的方法。它保证 在任何情况下,使用对象时先构造对象,最后析构对象。

3.1、unique_lock和lock_guard的使用

#include <iostream>
#include <thread>
#include <mutex>
#include <stdexcept>

std::mutex mtx;

void print_event(int x)
{
        if(x%2==0)
                std::cout << x << " is even\n";
        else
                throw (std::logic_error("not even"));

}

void print_thread_id(int id)
{
        try{
                std::unique_lock<std::mutex> lck(mtx);
                print_event(id);
        }
        catch(std::logic_error&)
        {
                std::cout << "[exception caught]\n";
        }
}

int main(int argc,char **argv)
{
        std::thread threads[10];
        for(int i=0;i<10;i++)
        {
                threads[i]=std::thread(print_thread_id,i+1);
        }
        for (auto& th : threads) th.join();
        return 0;

}

编译和执行:

$ g++ -o mutex_test lock_test.cc -std=c++11 -lpthread
$ ./mutex_test 
[exception caught]
6 is even
[exception caught]
4 is even
[exception caught]
2 is even
[exception caught]
8 is even
[exception caught]
10 is even

这里的unique_lock换成lock_guard是一样的。

3.2、unique_lock和lock_guard的区别

  1. unique_lock与lock_guard都能实现自动加锁和解锁,但是前者更加灵活,能实现更多的功能。
  2. unique_lock可以进行临时解锁和再上锁,如在构造对象之后使用lck.unlock()就可以进行解锁, lck.lock()进行上锁,而不必等到析构时自动解锁。lock_guard是不支持手动释放的。
  3. 一般来说,使用unique_lock比较多,除非追求极致的性能才会考虑使用lock_guard。
#include <iostream>
#include <mutex>
#include <thread>
#include <deque>
#include <condition_variable>
#include <unistd.h>

std::deque<int> q;
std::mutex mtx;
std::condition_variable cond;
int count=0;

void func1()
{
        while(true)
        {
                // {
                std::unique_lock<std::mutex> locker(mtx);
                q.push_front(count++);
                locker.unlock();//如果是lock_guard,不支持手动解锁
                cond.notify_one();

                sleep(1);
        }
}

void func2()
{
        while(true)
        {
                std::unique_lock<std::mutex> locker(mtx);
                cond.wait(locker,[](){return !q.empty();});
                auto data=q.back();
                q.pop_back();
                std::cout << "thread2 get value form thread1: " << data << std::endl;
        }
}

int main(int atgc,char ** argv)
{
        std::thread t1(func1);
        std::thread t2(func2);
        t1.join();
        t2.join();
        return 0;
}

条件变量的目的就是为了,在没有获得某种提醒时长时间休眠; 如果正常情况下, 我们需要一直循环 (+sleep), 这样的问题就是CPU消耗+时延问题,条件变量的意思是在cond.wait这里一直休眠直到 cond.notify_one唤醒才开始执行下一句; 还有cond.notify_all()接口用于唤醒所有等待的线程。

那么这里为什么必须使用unique_lock呢? 原因是条件变量在wait时会进行unlock再进入休眠, lock_guard并无该操作接口。

  1. wait: 如果线程被唤醒或者超时那么会先进行lock获取锁, 再判断条件(传入的参数)是否成立, 如果成立则 wait函数返回否则释放锁继续休眠。
  2. notify: 进行notify动作并不需要获取锁。
  3. 使用场景:需要结合notify+wait的场景使用unique_lock; 如果只是单纯的互斥使用lock_guard。

3.3、小结

lock_guard:

  1. std::lock_guard 在构造函数中进行加锁,析构函数中进行解锁。
  2. 锁在多线程编程中,使用较多,因此c++11提供了lock_guard模板类;在实际编程中,我们也可以根据自己的场景编写resource_guard RAII类,避免忘掉释放资源。

std::unique_lock:

  1. unique_lock 是通用互斥包装器,允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和与 条件变量一同使用。
  2. unique_lock比lock_guard使用更加灵活,功能更加强大。
  3. 使用unique_lock需要付出更多的时间、性能成本。

虽然递归锁能解决这种情况的死锁问题,但是尽量不要使用递归锁,主要原因如下:

  1. 需要用到递归锁的多线程互斥处理本身就是可以简化的,允许递归很容易放纵复杂逻辑的产生,并且产生晦涩,当要使用递归锁的时候应该重新审视自己的代码是否一定要使用递归锁。
  2. 递归锁比起非递归锁,效率会低。
  3. 递归锁虽然允许同一个线程多次获得同一个互斥量,但可重复获得的最大次数并未具体说明,一旦 超过一定的次数,再对lock进行调用就会抛出std::system错误。

四、递归互斥量std::recursive_mutex

递归锁允许同一个线程多次获取该互斥锁,可以用来解决同一线程需要多次获取互斥量时死锁的问题。
死锁示例:

#include <iostream>
#include <thread>
#include <mutex>
struct Complex
{
	std::mutex mutex;
	int i;
	Complex() : i(0){}
	void mul(int x)
	{
		std::lock_guard<std::mutex> lock(mutex);
		i *= x;
	}
	void div(int x)
	{
		std::lock_guard<std::mutex> lock(mutex);
		i /= x;
	}
	void both(int x, int y)
	{
		std::lock_guard<std::mutex> lock(mutex);
		mul(x);
		div(y);
	}
};
int main(void)
{
    Complex complex;
    complex.both(32, 23);
    return 0;
}

运行后出现死锁的情况。在调用both时获取了互斥量,在调用mul时又要获取互斥量,但both的并没有 释放,从而产生死锁。
使用递归锁解决同一个线程的死锁问题:

#include <iostream>
#include <thread>
#include <mutex>
struct Complex
{
	std::recursive_mutex mutex;
	int i;
	Complex() : i(0){}
	void mul(int x)
	{
		std::lock_guard<std::recursive_mutex> lock(mutex);
		i *= x;
	}
	void div(int x)
	{
		std::lock_guard<std::recursive_mutex> lock(mutex);
		i /= x;
	}
	void both(int x, int y)
	{
		std::lock_guard<std::recursive_mutex> lock(mutex);
		mul(x);
		div(y);
	}
};
int main(void)
{
	Complex complex;
	complex.both(32, 23); //因为同一线程可以多次获取同一互斥量,不会发生死锁
	std::cout << "main finish\n";
	return 0;
}

五、带超时的互斥量std::timed_mutex和std::recursive_timed_mutex

std::timed_mutex比std::mutex多了两个超时获取锁的接口:try_lock_for和try_lock_until。

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
std::timed_mutex mutex;
void work()
{
	std::chrono::milliseconds timeout(100);
	while (true)
	{
		if (mutex.try_lock_for(timeout))
		{
			std::cout << std::this_thread::get_id() << ": do work with the mutex" << std::endl;
			std::chrono::milliseconds sleepDuration(250);
			std::this_thread::sleep_for(sleepDuration);
			mutex.unlock();
			std::this_thread::sleep_for(sleepDuration);
		}
		else
		{
			std::cout << std::this_thread::get_id() << ": do work without the mutex" << std::endl;
			std::chrono::milliseconds sleepDuration(100);
			std::this_thread::sleep_for(sleepDuration);
		}
	}
}
int main(void)
{
	std::thread t1(work);
	std::thread t2(work);
	t1.join();
	t2.join();
	std::cout << "main finish\n";
	return 0;
}

总结

互斥锁mutex是为了临界资源的安全访问,C++11的unique_lock和lock_guard可以防止释放锁导致死锁问题。一般工作中比较少用recursive_mutex、std::timed_mutex和 std::recursive_timed_mutex。

c++ mutex,C/C++,c++,算法,mutex,互斥量,c++11文章来源地址https://www.toymoban.com/news/detail-590691.html

到了这里,关于C++11互斥量mutex使用详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++11之超时锁timed_mutex

    超时锁:用来记录线程加锁 解锁 等竞争锁的过程,多用于调试多线程时使用。 启动多个线程,然后,竞争一把超时锁,分别打印记录加锁成功的线程和失败的线程id: 打印结果:

    2024年02月13日
    浏览(40)
  • 【Linux从入门到精通】线程互斥与互斥锁的使用与原理详解

      上篇文章我们对线程 | 线程介绍线程控制介绍后,本篇文章将会对多线程中的线程互斥与互斥锁的概念进行详解。同时结合实际例子解释了可重入与不被重入函数、临界资源与临界区和原子性的概念。希望本篇文章会对你有所帮助。 文章目录 引入 一、重入与临界 1、1 可重

    2024年02月07日
    浏览(43)
  • Modern C++ std::mutex底层原理

    我时常有这样的疑问: std::mutex怎么就能保证后面的语句100%安全哪? CPU reordering就不会把这些语句重排到mutex前面执行? 而且各个CPU都是有L1、L2缓存的,如果mutex后面要访问的的变量在这些缓存中怎么办? 带着这些疑问我们先看看std::mutex是怎么实现的。 先给个图,再细说。

    2024年01月17日
    浏览(37)
  • C++ 各类mutex和读写锁性能比较

    pthread_mutex_t: 互斥锁,同一瞬间只能有一个线程能够获取锁,其他线程在等待获取锁的时候会进入休眠状态。因此pthread_mutex_t消耗的CPU资源很小,但是性能不高,因为会引起线程切换。 pthread_spinlock_t: 自旋锁,同一瞬间也只能有一个线程能够获取锁,不同的是,其他线程在

    2024年02月02日
    浏览(40)
  • C++多线程编程(第三章 案例1,使用互斥锁+ list模拟线程通信)

    主线程和子线程进行list通信,要用到互斥锁,避免同时操作 1、封装线程基类XThread控制线程启动和停止; 2、模拟消息服务器线程,接收字符串消息,并模拟处理; 3、通过Unique_lock和mutex互斥方位list 消息队列 4、主线程定时发送消息给子线程; 代码包含了XThread类(基类)、

    2024年02月15日
    浏览(35)
  • 【C++】C++入门 auto关键字详解(C++11)

    前言 C语言中包括了auto,它可用于定义局部变量。但自从所有的非全局变量的缺省值假定为auto以来,auto就几乎很少使用了。 在C或者以前C++中,auto基本上可以被无视. 但是在C++11中auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器, a

    2024年02月08日
    浏览(49)
  • C++算法初级11——01背包问题(动态规划2)

    辰辰采药 辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时

    2024年02月02日
    浏览(48)
  • windows C++多线程同步<3>-互斥量

    概念,如下图: 另外就是互斥对象谁拥有,谁释放 那么一个线程允许多次获取互斥对象吗? 答案是允许,但是申请多次就要释放多次,否则其他线程获取不到互斥对象;同一个线程可以多次获取,即使不释放也可以,但是这种做法有问题极易导致线程同步出现异常,强烈不

    2024年02月14日
    浏览(74)
  • 【操作系统】同步和互斥详细讲解(算法+源码)

    博主介绍:✌全网粉丝喜爱+、前后端领域优质创作者、本质互联网精神、坚持优质作品共享、掘金/腾讯云/阿里云等平台优质作者、擅长前后端项目开发和毕业项目实战✌有需要可以联系作者我哦! 🍅附上相关C语言版源码讲解🍅 👇🏻 精彩专栏推荐订阅👇🏻 不然下次找

    2024年01月23日
    浏览(36)
  • RAD Studio 11.2详解其务实改进(Delphi & C++ Builder)-Alexandria

    目录 RAD Studio 11.2详解其务实改进(Delphi C++ Builder)-Alexandria 本文为博主针对官方的原创技术翻译,未经许可不得转载 一、官方关于本次release的版本改进说明         愿景         再次介绍“RAD Studio”的优势         RAD Studio 11.0回顾         RAD Studio 11.1.5 (C++) 的回顾      

    2024年01月20日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包