谈谈多线程下为何需要锁

什么是锁?为什么存在?

多个线程运行的时候,共享了同一块资源,在访问这块资源的时候就称为临界资源。为了解决这个问题,我们可以为这块资源加上一把锁,只允许一个线程访问这块资源。

那今天就讲讲互斥锁,自旋锁。
首先先看看不加锁的情况下多线程共享访问临界资源会发生什么.
代码很简单,开启10个线程,各加上100000,理想结果应该是10 * 100000,好的,来试试吧


```c
#include <stdio.h>
#include <pthread.h>

#define THREAD_NUM 10
int count = 0;
void *proc(void *arg)
{
    int i = 0;
    while( i++ < 100000) {
        count++;
        usleep(1);
    }   
}

int main() 
{
    int i = 0;
    pthread_t id[THREAD_NUM] = {0};

    for (i = 0; i < THREAD_NUM; i++) {
        pthread_create(&id[i], NULL, proc, NULL);
    }   

    while (1) {
        printf("total %d\n", count);
        sleep(1);
    }   
    return 0;
}

运行结果

[guojunfeng@guojunfeng mutex]$ ./mutex 
total 9
total 158333
total 317776
total 476812
total 634098
total 793269
total 943308
total 997398
total 997398
total 997398
total 997398
total 997398

很明显结果997398跟理想状态1000000少了一些,丢失的那些值去哪了呢?为什么会导致这种现象,是谁在作怪?
可能很多同学会说count是临界资源没有加锁造成的,那为什么没有加锁达不到理想值呢?这块临界资源到底发生了什么事?

因为count++翻译成指令,反汇编看看,其实是有三个指令,读 加 写。

[guojunfeng@guojunfeng mutex]$ objdump -S -d mutex > count
//找到count++的位置
400642:   8b 05 00 0a 20 00       mov    0x200a00(%rip),%eax        # 601048 <__TMC_END__>
400648:   83 c0 01                add    $0x1,%eax
40064b:   89 05 f7 09 20 00       mov    %eax,0x2009f7(%rip)        # 601048 <__TMC_END__>  

大概意思是:
读取保存count到临时变量eax
edx再自增+1
存储edx到count


本来理想状态是以下这样交互达到正确的++
绝大多数的状态是这样的,线程1完成三个指令后切换线程2执行

线程1线程2
mov 0x200a00(%rip),%eax
add $0x1,%eax
mov %eax,0x2009f7(%rip)
mov 0x200a00(%rip),%eax
add $0x1,%eax
mov %eax,0x2009f7(%rip)

但有少数情况并不是上面这样的,竞争资源比较大的时候,会变成以下状态

线程1线程2
mov 0x200a00(%rip),%eax
mov 0x200a00(%rip),%eax
add $0x1,%eax
mov %eax,0x2009f7(%rip)
add $0x1,%eax
mov %eax,0x2009f7(%rip)

如果此时的count值是10,那线程1执行了第一条指令取值10至eax,切换线程2进来执行完成三条指令,此时eax是11赋值给了count寄存器。
再次切换回线程1,线程1的临时变量eax还是10,再进行自增后11赋值给了count寄存器。
最终这俩个线程++后的值是11,并不是12。所以这就可以解释上面代码为什么没达到理想值了。



那我们要怎么做才能让线程1执行++的时候是不让别的线程进来呢?答案就是执行阶段加上一把锁。

互斥锁和自旋锁:

#include <stdio.h>
#include <pthread.h>

#define THREAD_NUM 10
int count = 0;
pthread_mutex_t mutex;
pthread_spinlock_t spinlock;
void *proc(void *arg)
{
    int i = 0;
    while( i++ < 100000) {
#if 1
        pthread_mutex_lock(&mutex);
        count++;
        pthread_mutex_unlock(&mutex);
#else
        pthread_spin_lock(&spinlock);
        count++;
        pthread_spin_unlock(&spinlock);
#endif
        usleep(1);
    }   
}

int main() 
{
    int i = 0;
    pthread_t id[THREAD_NUM] = {0};
    pthread_mutex_init(&mutex, NULL);
	pthread_spin_init(&spinlock, PTHREAD_PROCESS_SHARED);
	
    for (i = 0; i < THREAD_NUM; i++) {
        pthread_create(&id[i], NULL, proc, NULL);
    }   

    while (1) {
        printf("total %d\n", count);
        sleep(1);
    }   
    return 0;
}


打开互斥锁代码就其实加了4行,因为是演示,锁的销毁就没处理,int pthread_mutex_destroy(pthread_mutexattr_t *attr);
定义了一个mutex的互斥锁,进行初始化,在进行count++的时候lock,执行完再unlock。



看下运行结果

[guojunfeng@guojunfeng mutex]$ ./mutex 
total 10
total 158645
total 316088
total 475166
total 635056
total 795857
total 955740
total 1000000
total 1000000
total 1000000
total 1000000

其实打开spinlock开关结果也是正确的。
加锁的这期间是做了什么事情呢?

spinlock自旋,在线程切换的时候会一直在等待释放锁。
mutex互斥,在线程切换的时候可能会干其它的事,不会一直瞎等。


那分别应用在什么场景下?
mutex是对于处理时间比较长的,比较复杂的
spinlock对于时间较短,比较简单的
以线程切换作为一个标准,如果这个处理时间超过切换线程的代价,就采用mutex,相反采用spinlock。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值