分布式锁实现原理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/D_Guco/article/details/82559099

一 首先我们来回顾下普通锁的种类。

1 自旋锁

       自旋锁如果已经被别的线程获取,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,”自旋”一词就是因此而得名。自旋锁是一种非阻塞锁,也就是说,如果某线程需要获取自旋锁,但该锁已经被其他线程占用时,该线程不会被挂起,而是在不断的消耗CPU的时间,不停的试图获取自旋锁。

2 互斥锁 (Mutex Lock)
      互斥锁是阻塞锁,也是我们最常用的一种锁,当某线程无法获取互斥锁时,该线程会被直接挂起,不再消耗CPU时间,当其他线程释放互斥锁后,操作系统会唤醒那个被挂起的线程。阻塞锁可以说是让线程进入阻塞状态进行等待,当获得相应的信号(唤醒,时间)时,才可以进入线程的准备就绪状态,准备就绪状态的所有线程,通过竞争进入运行状态。它的优势在于,阻塞的线程不会占用 CPU 时间, 不会导致 CPU 占用率过高,但进入时间以及恢复时间都要比自旋锁略慢。在竞争激烈的情况下阻塞锁的性能要明显高于自旋锁。
      上面两种锁适用于不同场景:如果是多核处理器,预计线程等待锁的时间很短,短到比线程两次上下文切换时间要少的情况下,使用自旋锁是划算的。如果是多核处理器,如果预计线程等待锁的时间较长,至少比两次线程上下文切换的时间要长,建议使用互斥锁。如果是单核处理器,一般建议不要使用自旋锁。因为,在同一时间只有一个线程是处在运行状态,那如果运行线程发现无法获取锁,只能等待解锁,但因为自身不挂起,所以那个获取到锁的线程没有办法进入运行状态,只能等到运行线程把操作系统分给它的时间片用完,才能有机会被调度。这种情况下使用自旋锁的代价很高。如果加锁的代码经常被调用,但竞争情况很少发生时,应该优先考虑使用自旋锁,自旋锁的开销比较小,互斥量的开销较大。

3 可重入锁 (Reentrant Lock)
      可重入锁是一种特殊的互斥锁,它可以被同一个线程多次获取,而不会产生死锁。首先它是互斥锁:任意时刻,只有一个线程锁。即假设A线程已经获取了锁,在A线程释放这个锁之前,B线程是无法获取到这个锁的,B要获取这个锁就会进入阻塞状态。其次,它可以被同一个线程多次持有。即,假设A线程已经获取了这个锁,如果A线程在释放锁之前又一次请求获取这个锁,那么是能够获取成功的。

     此外还有一些不常见的,偏向锁,重量级锁等等,https://www.cnblogs.com/charlesblc/p/5994162.html,这些锁应该是java中实现的锁,作为一个业余的java开发者真是有点孤陋寡闻,这里写个链接吧。

二 什么是分布式锁

     分布式锁,是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。

     分布式锁的本质可以看作是特殊的普通锁,它的竞争者不是普通的进程和线程,它的竞争者分布在不同的主机上,需要通过网络来相互通信,不同的主机分布和网络的不确定性给分布式锁的实现和普通锁有着很大的不同。

   目前常见的分布式锁的实现有基于数据库,基于缓存(redis等缓存数据库),基于zoonkeeper三种:https://www.cnblogs.com/austinspark-jessylu/p/8043726.html,这里我简单通过redis实现一种。

      基于redis实现分布式锁很简单,通过一个命令setnx(set if no exist).只有在key不存在的时候才set成功返回1,否则返回失败0,但是要考虑到一些意外情况还是需要一个很严谨的逻辑。

    在看下面的代码前最好浏览一下https://blog.csdn.net/weixin_39471249/article/details/79121291

//
// Created by dguco on 18-9-9.
//

#ifndef SERVER_REDI_LOCK_H
#define SERVER_REDI_LOCK_H
#include <iostream>
#include <redis_client.h>
#include <future>

using namespace std;

// 获取当前秒数
int GetSecond()
{
	struct timeval tmval = {0};
	int nRetCode = gettimeofday(&tmval, NULL);
	if (nRetCode != 0) {
		return 0;
	}
	return (int) (tmval.tv_sec);
}

#define LOCK_TIME_OUT 1 //1s

CRedisClient g_RedisCli;

class CRedisLock
{
public:
	/**
	 *
	 * @param lockKey
	 * @param isBlock  司机阻塞
	 * @return 是否锁成功
	 */
	bool Lock(std::string &lockKey, bool isBlock = false);
	template<class F, class... Args>
	void DoWithLock(std::string &lockKey, F &&f, Args &&... args);
	template<class F, class... Args>
	void TryDoWithLock(std::string &lockKey, F &&f, Args &&... args);
private:
	int m_iLockTimeOut;
};

template<class F, class... Args>
void CRedisLock::TryDoWithLock(std::string &lockKey, F &&f, Args &&... args)
{
	bool isLock = Lock(lockKey, false);
	if (isLock) {
		using return_type = typename std::result_of<F(Args...)>::type;
		auto task = std::make_shared<std::packaged_task<return_type()> >(
			std::bind(std::forward<F>(f), std::forward<Args>(args)...)
		);
		(*task)( );
		int now = GetSecond( );
		if (now < m_iLockTimeOut) {
			g_RedisCli.Del(lockKey);
		}
	}
}

template<class F, class... Args>
inline void CRedisLock::DoWithLock(std::string &lockKey, F &&f, Args &&... args)
{
	bool isLock = Lock(lockKey, true);
	if (isLock) {
		using return_type = typename std::result_of<F(Args...)>::type;
		auto task = std::make_shared<std::packaged_task<return_type()> >(
			std::bind(std::forward<F>(f), std::forward<Args>(args)...)
		);
		(*task)( );
		int now = GetSecond( );
		if (now < m_iLockTimeOut) {
			g_RedisCli.Del(lockKey);
		}
	}
}

bool CRedisLock::Lock(std::string &lockKey, bool isBlock)
{
	int lock = 0;
	m_iLockTimeOut = 0;
	bool isLock = false;
	while (lock != 1) {
		int now = GetSecond( );
		m_iLockTimeOut = now + LOCK_TIME_OUT + 1;
		lock = g_RedisCli.Setnx(lockKey, to_string(m_iLockTimeOut));
		//是否获取成功
		if (lock == 1) {
			isLock = true;
		}
		//判断是否超时,并设置新的超时时间
		if (!isLock) {
			string res = "";
			g_RedisCli.Get(lockKey, &res);
			//如果没有被其他竞争者
			if (res != "") {
				int out1 = atoi(res.c_str( ));
				string res1 = "";
				g_RedisCli.Getset(lockKey, &res1);
				//如果更新超时之前没有被其他竞争者抢先且超时
				if (now > out1 && res == res1) {
					isLock = true;
				}
			}

		}
		if (isLock or !isBlock) {
			break;
		}
		else {
			usleep(1000);
		}
	}
	return isLock;
}

#endif //SERVER_REDI_LOCK_H

string key = "test";
string lockKey = "Lock.test";
string key1 = "test1";
CRedisLock redisLock;

int main()
{
//	testDlopen();
//	testSoHotLoad();
//	listenFileChange();
    if (!g_RedisCli.Initialize("127.0.0.1", 6379, 2, 1)) {
        std::cout << "connect to redis failed" << std::endl;
        return -1;
    }
    redisLock.DoWithLock(lockKey, [key]
    {
        string res;
        g_RedisCli.Get(key,&res);
        int a  = atoi(res.c_str());
        a++;
        g_RedisCli.Set(key, to_string(a));
    });
    std::cout << "Over" << std::endl;
};

 最后https://blog.csdn.net/daiyudong2020/article/details/51760648 ,文章末尾提到的这种实现的缺陷可以了解下。

 推荐阅读:http://ifeve.com/redis-lock/

 官方也有各种版本的解决方案:https://redis.io/topics/distlock

阅读更多 登录后自动展开
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页