几种锁的实现

锁是为了避免多线程或是多进程操作临界资源时出现不可预知的错误,确保程序按照预期的顺序执行。锁的种类有很多,这里介绍其中几种。

1.互斥锁

互斥锁mutex是当一个进程或线程在进入临界区后加锁,其他进程或线程在解锁前无法进入临界区的锁。实现方式如下:

pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
pthread_mutex_lock(&mutex);
(*pcount) ++;  //模拟操作临界资源
pthread_mutex_unlock(&mutex);

值得注意的是,互斥锁在遇到已加锁的情况时,会使等待的线程进行休眠,也就是要进行线程切换,线程切换需要mv保存当前寄存器的值和操作系统的相关属性(文件系统相关、虚拟内存等),尤其是操作系统相关的资源切换代价较大,所以,有一种尝试加锁的方法,尝试一下,如果被加锁了就继续做其他事而不是切换线程。这样,自己就可以在应用层实现调度的方法,避免了系统对线程进行切换。

if (0 != pthread_mutex_trylock(&mutex)) 
{
    i --;
    continue;
}
(*pcount) ++;
thread_mutex_unlock(&mutex);

2.自旋锁

自旋锁spinlock与互斥锁类似,区别是互斥锁在遇到已被加锁的资源会切换线程,而自旋锁会一直循环等待,直到锁被释放。加锁后,CPU会一直等,即使时间片到了也不会切换线程。

pthread_spinlock_t spinlock;
pthread_spin_init(&spinlock, PTHREAD_PROCESS_SHARED);

pthread_spin_lock(&spinlock);
(*pcount) ++;
pthread_spin_unlock(&spinlock);

这里再讲一下,什么时候用互斥锁,什么时候用自旋锁呢?界限是临界区的操作是否繁杂,标准是与线程切换的操作相比,比线程切换复杂就用互斥锁,比线程切换简单就用自旋锁。当然,使用互斥锁时最好是用trylock()。

3.读写锁

读写锁rdlock不建议使用,因为代码写起来会较为麻烦,只有在读多写少并且明确得知互斥锁性能不如读写锁是才使用。主要特点是允许多个线程同时读,但不允许多个线程同时写或者同时读写。

pthread_rwlock_t rwlock;
pthread_rwlock_init(&rwlock, NULL);

pthread_rwlock_rdlock(&rwlock);
printf("count --> %d\n", count);
pthread_rwlock_unlock(&rwlock);

其他线程

pthread_rwlock_wrlock(&rwlock);
(*pcount) ++;
pthread_rwlock_unlock(&rwlock);

最后再讲一下,不论使用哦哪种锁,都要注意避免死锁。

4.原子操作

这里讲一下,原子操作也能达到锁的效果,因为是将操作一口气执行完,其他线程也不能操作临界区。

int inc(int *value, int add) 
{
    int old;
    __asm__ volatile 
    ( 
        "lock; xaddl %2, %1;" 
        : "=a" (old)
        : "m" (*value), "a" (add)
        : "cc", "memory"
    );
    return old;
}

这里使用的是汇编代码。注意因为不同操作系统的汇编器不同,汇编语言的语法也会有所不同。如linux是at&t指令集,windows是x86指令集,mov指令就会有所不同。
这里再讲一下CAS。CAS也是原子操作,命令是xchg(),可以用于线程安全的单例模式。

xchg(instance, NULL, newobj);

5.共享内存

以上方法都是针对多线程的情况,如果是多进程加锁,则需要使用共享内存。这里以内存映射为例。

int *pcount = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_ANON|MAP_SHARED, -1, 0);
int i = 0;
pid_t pid = 0;
for (i = 0;i < THREAD_SIZE;i ++) 
{
    pid = fork();
    if (pid <= 0) 
    {
        usleep(1);
        break;
    }
}
if (pid > 0)
{
    for (i = 0;i < 100;i ++) 
    {
        printf("count --> %d\n",  (*pcount));
        sleep(1);
    }
} else 
{
    int i = 0;
    while (i++ < 100000)  
    {
        inc(pcount, 1);
        usleep(1);
    }
}

这里使用的匿名映射,只能用于具有父子关系的进程之间,如果打开一个fd,对一个文件做映射,则不限于父子关系的进程。
这里再提示一下,多进程共用一个内存池也可以使用共享内存的方法。
最后再讲一下volatile。volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。简单来说,就是防止编译器对变量做优化。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值