linux多线程:自旋锁

目录

1、概述

2、spin lock 锁相关的API

2.1、初始化自旋锁

2.2、获得一个自旋锁

2.3、尝试获取一个自旋锁

2.4、释放(解锁)一个自旋锁

2.5、销毁一个自旋锁

3、Mutex 锁和 Spin Lock 的区别

4、Spin Lock 代码演练


1、概述

自旋锁是 SMP 架构中的一种 low-level 的同步机制。
当线程A想要获取一把自旋锁而该锁又被其它线程锁持有时,线程A会在一个循环中自旋以检测锁是不是已经可用了。

对于使用自选锁需要注意:

  • 由于自旋时不释放CPU,因而持有自旋锁的线程应该尽快释放自旋锁,否则等待该自旋锁的线程会一直在那里自旋,这就会浪费CPU时间。
  • 持有自旋锁的线程在sleep之前应该释放自旋锁以便其它线程可以获得自旋锁,或者拿到自旋锁之后不允许睡眠。
  • 参与自旋锁竞争的两个或多个线程如果绑定在同一个CPU核上,则其调度属性设置为实时调度(RT_FIFO)时必须将其优先级设置相同,否则自旋锁无法在线程之间切换,这是因为低优先级任务拿到锁之后被切出去之后,高优级任务会一直在那自旋拿锁会将CPU核打满,导致低优先级任务一直因得不到执行而不能放锁。
  • 同互斥锁相同用法顺序:拿锁、访问公共资源、放锁。特别强调的是 拿锁之后代码中的异常分支处理中必须放锁。
  • 自旋锁不支持递归调用,线程重复拿锁时会发生自旋

使用任何锁需要消耗系统资源(内存资源和CPU时间),这种资源消耗可以分为两类:

  • 建立锁所需要的资源
  • 线程被阻塞时锁所需要的资源

2、spin lock 锁相关的API

1 int pthread_spin_destroy(pthread_spinlock_t *);
2 int pthread_spin_init(pthread_spinlock_t *, int);
3 int pthread_spin_lock(pthread_spinlock_t *);
4 int pthread_spin_trylock(pthread_spinlock_t *);
5 int pthread_spin_unlock(pthread_spinlock_t *);

2.1、初始化自旋锁

  pthread_spin_init 用来申请使用自旋锁所需要的资源并且将它初始化为非锁定状态。pshared的取值及其含义:

  • PTHREAD_PROCESS_SHARED:该自旋锁可以在多个进程中的线程之间共享。
  • PTHREAD_PROCESS_PRIVATE: 仅初始化本自旋锁的线程所在的进程内的线程才能够使用该自旋锁。

2.2、获得一个自旋锁

  pthread_spin_lock 用来获取(锁定)指定的自旋锁. 如果该自旋锁当前没有被其它线程所持有,则调用该函数的线程获可以得该自旋锁,否则线程在该函数处自旋如果调用该函数的线程在调用该函数时已经持有了该自旋锁,则结果是不确定的(实测继续自旋,不支持递归调用)。

2.3、尝试获取一个自旋锁

  pthread_spin_trylock 会尝试获取指定的自旋锁,如果无法获取则理解返回失败。

2.4、释放(解锁)一个自旋锁

  pthread_spin_unlock 用于释放指定的自旋锁。

2.5、销毁一个自旋锁

     pthread_spin_destroy 用来销毁指定的自旋锁并释放所有相关联的资源(所谓的所有指的是由pthread_spin_init自动申请的资源)在调用该函数之后如果没有调用 pthread_spin_init 重新初始化自旋锁,则任何尝试使用该锁的调用的结果都是未定义的。如果调用该函数时自旋锁正在被使用或者自旋锁未被初始化则结果是未定义的。

3、Mutex 锁和 Spin Lock 的区别

Pthreads提供的Mutex锁操作相关的API主要有:

1 pthread_mutex_lock (pthread_mutex_t *mutex);
2 pthread_mutex_trylock (pthread_mutex_t *mutex);
3 pthread_mutex_unlock (pthread_mutex_t *mutex);
Pthreads 提供的与Spin Lock 锁操作相关的API主要有:
pthread_spin_lock (pthread_spinlock_t *lock);
pthread_spin_trylock (pthread_spinlock_t *lock);
pthread_spin_unlock (pthread_spinlock_t *lock);

   从实现原理上来讲,Mutex 属于 sleep-waiting 类型的锁。例如在一个双核的机器上有两个线程(线程A和线程B),它们分别运行在 Core0 和 Core1上。假设线程 A 想要通过 pthread_mutex_lock 操作去得到一个临界区的锁,而此时这个锁正被线程 B 所持有,那么线程 A 就会被阻塞 (blocking),Core0 会在此时进行上下文切换(Context Switch)将线程A置于等待队列中,此时Core0就可以运行其他的任务(例如另一个线程C)而不必进行忙等待。而Spin lock则不然,它属于busy-waiting类型的锁,如果线程A是使用 pthread_spin_lock 操作去请求锁,那么线程 A 就会一直在 Core0上进行忙等待并不停的进行锁请求,直到得到这个锁为止,此时在同核上的线程C的运行状态取决于调度属性,此处不再赘述。有一点是可以肯定的,线程A 会“不遗余力”的占用Core0。

   如果大家去查阅Linux glibc中对pthreads API的实现NPTL(Native POSIX Thread Library) 的源码的话(使用”getconf GNU_LIBPTHREAD_VERSION”命令可以得到我们系统中NPTL的版本号),就会发现 pthread_mutex_lock() 操作如果没有锁成功的话就会调用 system_wait() 的系统调用并将当前线程加入该 mutex 的等待队列里。而 spin lock 则可以理解为在一个while(1)循环中用内嵌的汇编代码实现的锁操作(印象中看过一篇论文介绍说在linux内核中spin lock操作只需要两条CPU指令,解锁操作只用一条指令就可以完成)。有兴趣的朋友可以参考另一个名为sanos的微内核中pthreds API的实现:mutex.c spinlock.c,尽管与NPTL中的代码实现不尽相同,但是因为它的实现非常简单易懂,对我们理解spin lock和mutex的特性还是很有帮助的。

        对于自旋锁来说,它只需要消耗很少的资源来建立锁;随后当线程被阻塞时,它就会一直重复检查看锁是否可用了,也就是说当自旋锁处于等待状态时它会一直消耗CPU时间。

        对于互斥锁来说,与自旋锁相比它需要消耗大量的系统资源来建立锁;随后当线程被阻塞时,线程的调度状态被修改,并且线程被加入等待线程队列;最后当锁可用时,在获取锁之前,线程会被从等待队列取出并更改其调度状态;但是在线程被阻塞期间,它不消耗CPU资源。

        因此自旋锁和互斥锁适用于不同的场景。自旋锁适用于那些仅需要阻塞很短时间的场景,而互斥锁适用于那些可能会阻塞很长时间的场景。

4、Spin Lock 代码演练

#include<stdio.h>
#include<stdlib.h>
#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include <sys/prctl.h>

pthread_spinlock_t spinLock;
int lockCnt = 10;

void printSelfThreadInfo(const char *s,int cnt);
void *threadEntryOne(void *arg);
void *threadEntryTwo(void *arg);

#define SYS_SUCC  0
#define SYS_ERR  -1

pthread_t   gThreadId1,gThreadId2;

int main(int argc,char **argv)
{
    unsigned int i=0;
    int sysRet = -1;

	/*1,initialize spin lock*/
	pthread_spin_init(&spinLock,PTHREAD_PROCESS_PRIVATE);

    printf("process pid = %d\n",getpid());
    
    sysRet = pthread_create(&gThreadId1,NULL,threadEntryOne,NULL);
    if(sysRet == SYS_ERR)
    {
        printf("can't create threadOne...\n");
        exit(1);
    }

    sysRet = pthread_create(&gThreadId2,NULL,threadEntryTwo,NULL);
    if(sysRet == SYS_ERR)
    {
        printf("can't create threadTwo...\n");
        exit(1);
    }

	pthread_join(gThreadId1,NULL);
	pthread_join(gThreadId2,NULL);
    
    pthread_spin_destroy(&spinLock);
    return 0;
}


void printSelfThreadInfo(const char *s,int cnt)
{
    pid_t tid;

    tid = pthread_self();
    printf("%s:tid = %u,lockCnt=%d\n",s,tid,cnt);

}

void *threadEntryOne(void *arg)
{
    char threadName[16]; 
    prctl(PR_SET_NAME,"threadEntryOne");
    prctl(PR_GET_NAME, threadName);
    for(;;)
    {
        pthread_spin_lock(&spinLock);
        //pthread_spin_lock(&spinLock);
        
        printSelfThreadInfo(threadName,lockCnt);

        if(lockCnt <=0)
            break;
               
        lockCnt--;

        sleep(1);
        pthread_spin_unlock(&spinLock);
        //sleep(1);
    }

    pthread_spin_unlock(&spinLock);
}

void *threadEntryTwo(void *arg)
{
    char threadName[16]; 
    prctl(PR_SET_NAME,"threadEntryTwo");//设置线程名
    prctl(PR_GET_NAME, threadName);//获取线程名

    for(;;)
    {
        pthread_spin_lock(&spinLock);
        //pthread_spin_lock(&spinLock);spin锁不支持递归调用,重复调用也会自旋
        printSelfThreadInfo(threadName,lockCnt);

        if(lockCnt <=0)
            break;
        
        lockCnt--;

        sleep(1);
        pthread_spin_unlock(&spinLock);//实测:放锁后立刻又获取锁通常可以成功
        //sleep(1);
    }
    
    pthread_spin_unlock(&spinLock);//循环结束后需要记得放锁,否则其它线程永远要自旋
}                                                   

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值