C++11线程中的几种锁

C++11线程中的几种锁

线程之间的锁有互斥锁、条件锁、自旋锁、读写锁递归锁
一般而言,锁的功能与性能成反比。
1.互斥锁(Mutex)。
互斥锁用于控制多个线程对他们共享资源互斥访问的一个信号量。防止在某一时刻多个线程同时访问一个共享资源。
例如线程池中有多个空闲线程和一个任务队列。任何一个线程都需要使用互斥锁访问任务队列,以避免多个线程同时访问一个任务队列,在某一时刻只有一个线程可以获取互斥锁,而在这个线程释放锁之前其他线程只能在阻塞等待状态。
头文件
#include< Mutex >
Mutex类只是简单包装了一下他的父类_Mutex_base其中提供了lock()加锁和unlock()解锁实现我们的资源共享问题,但是我更建议使用c++标准库提供的lock_guard模板。

template<class _Mutex>
	class lock_guard
	{	// class with destructor that unlocks mutex
public:
	typedef _Mutex mutex_type;

	explicit lock_guard(_Mutex& _Mtx)
		: _MyMutex(_Mtx)
		{	// construct and lock
		_MyMutex.lock();
		}

	lock_guard(_Mutex& _Mtx, adopt_lock_t)
		: _MyMutex(_Mtx)
		{	// construct but don't lock
		}

	~lock_guard() _NOEXCEPT
		{	// unlock
		_MyMutex.unlock();
		}

	lock_guard(const lock_guard&) = delete;
	lock_guard& operator=(const lock_guard&) = delete;

private:
	_Mutex& _MyMutex;
	};

我们可以看出模板类在构造时帮我们进行了加锁,虚构时帮我们进行解锁,很方便。
代码参考。

#include <mutex>
#include <vector>

std::mutex Mutex;
std::vector<int> lists;//共享资源
void AddLists(int x)
{
    std::lock_guard<std::mutex> LockGuard(Mutex);//构造进行加锁
    lists.push_back(x);//对资源进行操作
}//当走到这时LockGuard进行析构 析构里对锁进行释放

2.条件锁(condition_variable)
条件锁可以理解为条件变量,它是配合std::mutex进行操作的,当线程不满足该条件时线程就会进入阻塞等待状态并释放锁,一旦条件满足以信号量的方式唤醒该线程然后重新获取锁。
代码参考。

//交替打印
#include <mutex>
#include <thread>
#include <condition_variable>
#include <sstream>
#include <atomic>

std::mutex Mutex;
static int k = 1;
std::condition_variable condition;
bool status;

void printF()
{
    while (k < 100)
    {
        std::unique_lock<std::mutex> uLock(Mutex);
        condition.wait(uLock, [](){ return (status == true); });
        std::stringstream ss;
        ss << std::this_thread::get_id();
        printf("%d,threaId : %s \n", k++, ss.str().c_str());
        status = false;
        uLock.unlock();
        condition.notify_all();
    }
}
void printF1()
{
    while (k < 100)
    {
        std::unique_lock<std::mutex> uLock(Mutex);
        condition.wait(uLock, [](){ return (status == false); });
        std::stringstream ss;
        ss << std::this_thread::get_id();
        printf("%d,threaId : %s \n", k++, ss.str().c_str());
        status = true;
        uLock.unlock();
        condition.notify_all();
    }
}

int main()
{
    std::thread th1(printF);
    std::thread th2(printF1);
    th1.join();
    th2.join();
    system("pause");
    return 0;
}

条件锁中wait、notify_one、notify_all三个方法
wait是根据条件使前线程进行阻塞等待
notify_one是唤醒其他单个线程
notify_all是唤醒所有阻塞中的线程
为什么条件变量要和互斥锁一起配合使用
因为条件变量是临界资源,而邻居资源需要被保护,所以使用互斥锁一起配合。
为什么wait()需要传入互斥锁作为参数?
将互斥锁作为参数传入wait是为了让条件变量库在适当的时间释放(unlock())、和获取锁(lock())。
3.自旋锁
我们通过与互斥锁对比的方式更容易了解自旋锁。

一台计算机中有core1处理器和core2处理器分别跑着线程1和线程2,线程1与线程2有共享资源。
对于互斥锁来说线程1获取互斥锁访问共享资源过程中线程2进入阻塞状态,core2处理器会先处理其他任务。自旋锁却截然不同,在线程1获取自旋锁访问资源时线程2进入阻塞状态,但是core2处理器不会处理其他任务,而是不断循环请求获取这个自旋锁。

通过比较我们清楚的知道自旋锁的原理以及自旋锁是比较耗费CPU的。

//使用std::atomic_flag的自旋锁互斥实现
class spinlock_mutex
{
    std::atomic_flag flag;
public:
	spinlock_mutex()
		:flag(ATOMIC_FLAG_INIT) {}
	void lock()
	{
	    while(flag.test_and_set(std::memory_order_acquire));
	}
	void unlock()
	{
	    flag.clear(std::memory_order_release);
	}
}
//使用
spinlock_mutex SMutex;
std::vector<int> lists;//共享资源
void AddLists(int x)
{
	SMutex.lock();
    lists.push_back(x);//对资源进行操作
    SMutex.unlock();
}

4.读写锁
当有一个线程己经持有互斥锁时,互斥锁将所有试图进入临界区的线程都阻塞住,但是考虑一种情形,当前持有互斥锁的线程只是要读访问共享资源,而同时其它线程也只是想读,但由于互斥锁的排它性,所有线程都不能去读,但实际上多个线程同时读并不会有什么问题。

在对于数据的读写操作中,更多的是读操作,写操作较少,为满足当前能够允许多个读出,但只允许一个写入的操作,线程提供了读写锁来实现

读写锁的特点

1 如果有其它线程读数据,则允许其它线程执行读操作,但不允许写操作

2 如果有其它线程写数据,则其它线程都不允许读,写操作

读写锁分为读锁和写锁,规则如下

1 如果某线程申请了读锁,其它线程可以再申请读锁,但不能申请写锁

2 如果某线程申请了写锁,其它线程不能申请读锁,也不能申请写锁

实操还得看boost库
多线程 boost的读写(reader-writer)锁
**背景:**保护很少更新的数据结构时,c++标准库没有提供相应的功能。
例如:有个DNS条目缓存的map,基本上很少有更新,大部分都是读取,但是偶尔也会有更新,这种情况下,如果在读取的函数里加上std::mutex就过于悲观了,每次只能有一个线程读取,但是想要的效果是,多个线程可以同时读取。这个时候c++标准库就显得无能为力了。
boost库就能登场了。

boost有个共享锁:boost::shared_mutex和boost::shared_lock,用boost::shared_mutex代替std::mutex后,当有某一个线程读取dns时,就锁住了这个共享锁,当第二个线程也要读取时,这是第一个还没读完,也就是还没有解锁,如果用的是std::mutex的话,第二线程是无法进行读取的,但是换成boost::shared_mutex后,第二个线程也可以读取了,即便第一个读取的线程还有结束。
但是也有限制,读取操作可以多个线程同时读取,但是某个时间点,有个线程要更新dns,是否可以更新取决于,这个时间点是否有读的线程还没有结束。
1,如果读取的线程都结束了,也就是所有的共享锁都被解锁了,这时可以更新dns,并且在更新的同时,所有读取的线程都要被阻塞;
2,如果读取的线程还有任意一个没有结束了,也就是说并不是所有的共享锁都被解锁了,这时,更新的线程就会被阻塞,直到,所有的读取线程结束后,更新线程才会开始。

#include <map>
#include <string>
#include <thread>
#include <mutex>
#include <functional>
#include <boost/thread/shared_mutex.hpp>

class dns_cache{
    std::map<std::string, std::string> entries;
    boost::shared_mutex entry_mutex;
public:
    std::string find_entry(std::string const& domain)const{
        boost::shared_lock<boost::shared_mutex> lk(entry_mutex);
        std::map<std::string, std::string>::const_iterator const it =
            entries.find(domain);
        return it == entries.end() ?  "" : it->second;
    }
    void update_or_add_entry(std::string const& domain, std::string const& dns_details){
        std::lock_guard<boost::shared_mutex> lk(entry_mutex);
        entries[domain] = dns_details;
    }
};
  • 19
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值