Skip to main content

uedbet的下载网站

redis 分布式锁

锁的来源

多个进程(或者线程)对临界区(资源抢占等)的访问。由于临界区同时只能有一个进程 或者线程运行。

进程在执行时,任何时间点有可能出现时间片用完(产生时间中断),然后被挂起,CPU 执行另外的进程,如果对临界区的访问不加任何限制,导致新的进程也开始访问临界区, 这样就容易出问题。

一个例子:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <signal.h>

// 线程一
static int i = 0;
static int t1 = 0;
static int t2 = 0;

static int sig = 0;

void sig_handler(int signum){
    sig = signum;
}
void thread_1(void) {
	while(sig==0){
    i = i + 1;
    t1 = t1 + 1;
    printf("i=%d t1=%d\n", i, t1);
    sleep(1);
	}
}

// 线程二
void thread_2(void) {
    while(sig==0){

    	i = i + 1;
        t2 = t2 + 1;
        printf("i=%d t2=%d\n", i, t2);
	sleep(1);
    }
}

int main(void) {
    pthread_t id_1, id_2;
    int ret;

    signal(SIGINT, sig_handler);


    /*创建线程一*/
    ret=pthread_create(&id_1, NULL, (void  *) thread_1, NULL);
    if(ret != 0) {
        printf("Create pthread 1 error!\n");
        return -1;
    }

    /*创建线程二*/
    ret=pthread_create(&id_2, NULL, (void  *) thread_2, NULL);
    if(ret != 0) {
        printf("Create pthread 2 error!\n");
        return -1;
    }

    /*等待线程结束*/
    pthread_join(id_1, NULL);
    pthread_join(id_2, NULL);
    return 0;
}

运行到第34秒的时候,结果就出错了,出现了两个i=67。

i=64 t1=32
i=65 t2=33
i=66 t1=33
i=67 t1=34
i=67 t2=34
i=68 t1=35
i=69 t2=35

下面看一个文件 b.c

static int i=0;
void f(){
	i = i + 1;
}

用gcc -S 得到汇编代码,可以看到 i = i + 1,被转换成了三条语句。

	movl	i(%rip), %eax
	addl	$1, %eax
	movl	%eax, i(%rip)

执行周期(取指,间址,执行,中断):当第一条执行执行完之后,发生了中断, 这个时候线程1里面 变量i=66, eax=66。

发生了中断,然后开始执行线程2。执行完之后i=67。这个时候有开始执行线程1, 因为线程1的第1条指令已经执行了,所以从第二条指令开始执行,最后i=67。

出现了 两次i=67。

因此,一个进程(或者线程,本质上是一个调度的单位)在访问临界区时,需要锁。

锁的特征

锁有两个接口:上锁,解锁。然后,有一个执行者(就是进程,或者线程)。

上锁的操作必须是排他性的。多个线程同时争夺一把锁时,最多只有一个线程成功。

解锁的操作必须是上锁的线程去解锁。

扩展到分布式

比如电商系统,多个进程同时购买一件商品时,需要将数据库中的数量都减去1等等。

多个进程也可能分布在多台机器上,这个时候就需要用分布式锁。

redis的实现

上锁

必须要用一条指令。由于进程在任何时候可能挂掉,导致锁得不到释放,所以 这个锁要加一个过期时间。

value必须要保证一个唯一性,解锁的时候需要用来判断是锁的获得者在解锁。

set key value expire_seconds NX

解锁

需要用lua脚本保证原子性

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end