zoukankan      html  css  js  c++  java
  • 自旋锁+信号量

    本文将为你介绍内核同步算法中的自旋锁和信号量。在这之前,先了解一些概念。

    执行线程:thread of execution,指任何正在执行的代码实例,可能是一个正在内核线程,一个中断处理程序等。有时候会将执行线程简称为线程。

    临界区:critical region,即访问和操作共享数据的代码段。

    多个执行线程并发访问同一资源通常是不安全的,通常使用自旋锁和信号量来实现对临界区互斥的访问。

    自旋锁

    自旋锁(spin lock)是一个对临界资源进行互斥访问的典型手段。自旋锁至多只能被一个执行线程持有。当一个执行线程想要获得一个已被使用的自旋锁时,该线程就会一直进行忙等待直至该锁被释放,就如同“自旋”所表达的意思那样:在原地打转。

    我们也可以这么理解自旋锁:它如同一把门锁,而临界区就如同门后面的房间。当一个线程A进入房间后,它会关闭房门,使得其他线程不得进入。此时如果其他某个进程B需要进入房间,那么只能在门外“打转”。当A进程打开们后,进程B才能进入房间。

    自旋锁的使用

    1.定义初始化自旋锁

    使用下面的语句就可以先定一个自旋锁变量,再对其进行初始化:

    1 spinlock_t lock;
    2 spin_lock_init(&lock);

    也可以这样初始化一个自旋锁:

    1 spinlock_t lock=SPIN_LOCK_UNLOCKED;

    2.获得自旋锁

    1 void spin_lock(spinlock_t*);
    2 int spin_trylock(spinlock_t*);

    使用spin_lock(&lock)这样的语句来获得自旋锁。如果一个线程可以获得自旋锁,它将马上返回;否则,它将自旋至自旋锁的保持者释放锁。

    另外可以使用spin_trylock(&lock)来试图获得自旋锁。如果一个线程可以立即获得自旋锁,则返回真;否则,返回假,此时它并不自旋。

    3.释放自旋锁

    1 void spin_unlock(spinlock_t*);

    使用spin_unlock(&lock)来释放一个已持有的自旋锁。注意这个函数必须和spin_lock或spin_trylock函数配套使用。

    关于自旋锁的说明

    1.保护数据。我们使用锁的目的是保护临界区的数据而不是临界区的代码。
    2.在使用自旋锁时候,必须关闭本地中断,否则可能出现双重请求的死锁。比如,中断处理程序打断正持有自旋锁的内核代码,如果这个中断处理程序又需要这个自旋锁,那么这个中断处理程序就会自旋等待自旋锁被释放;但是这个锁的持有者此刻被中断打断,因此不可能在中断完毕之前释放锁。此时就出现了死锁。
    3.自旋锁是忙等锁。正如同前面所说的那样,当其他线程持有自旋锁时,如果另有线程想获得该锁,那么就只能循环的等待。这样忙的功能对CPU来说是极大的浪费。因此只有当由自旋锁保护的临界区执行时间很短时,使用自旋锁才比较合理。
    4.自旋锁不能递归使用。这一点也很好理解,如果当前持有锁的线程又需要再持有该锁,那么它必须自旋,等待锁被释放;但是这个锁本身又被它自己持有,因此这个线程永远无法继续向前推进。

    信号量

    信号量(semaphore)是保护临界区的一种常用方法。它的功能与自旋锁相同,只能在得到信号量的进程才能执行临界区的代码。但是和自旋锁不同的是,一个进程不能获得信号量时,它会进入睡眠状态而不是自旋。

    利用上述自旋锁的门和锁的例子,我们可以这样解释信号量。我们将信号量比作钥匙。进程A想要进入房间,就必要持有一把钥匙。当钥匙被使用完之后,如果又有进程B想进房间,那么这个进程在等待其他进程从房间出来给它钥匙的同时会打盹。当房间里的某个进程出来时会摇醒这个睡觉的进程B。

    信号量的使用

    0.数据结构

    1 16struct semaphore {
    2 17        spinlock_t              lock;
    3 18        unsigned int            count;
    4 19        struct list_head        wait_list;
    5 20};

    count:初始化信号量时所设置的信号量值。
    wait_list:等待队列,该队列中即为等待信号量的进程。
    lock:自旋锁变量

    1.定义初始化信号量

    使用下面的代码可以定义并初始化信号量sem:

    struct semaphore sem;
    sem_init(&sem,val);

    其中val即为信号量的初始值。

    如果我们想使用互斥信号量,则使用下面的函数:

    init_MUTEX(&sem);

    这个函数会将sem的值初始为1,即等同于sem_init(&sem,1);

    如果想将互斥信号量的初值设为0,则可以直接使用下面的函数:

    init_MUTEX_LOCKED(&sem);

    除上面的方法,可以使用下面的两个宏定义并初始化信号量:

    DECLARE_MUTEX(name);

    DECLARE_MUTEX_LOCKED(name);

    其中name为变量名。

    2.获得信号量

    down(&sem);

    进程使用该函数时,如果信号量值此时为0,则该进车会进入睡眠状态,因此该函数不能用于中断上下文中。

    down_interruptibale(&sem);

    该函数与down函数功能类似,只不过使用down而睡眠的进程不能被信号所打断,而使用down_interruptibale的函数则可以被信号打断。

    如果想要在中断上下文使用信号量,则可以使用下面的函数:

    dwon_try(&sem);

    使用该函数时,如果进程可以获得信号量,则返回0;否则返回非0值,不会导致睡眠。

    3.释放信号量

    up(&sem);

    该函数会释放信号量,如果此时等待队列中有进程,则唤醒一个。

    信号量的同步

    信号量除了使进程互斥的访问临界区外,还可以用于进程间的同步。比如,当B进程执行完代码区C后,A进程才会执行代码段D,两者之间有一定的执行顺序。

  • 相关阅读:
    MS SQL入门基础:使用SQL Server Profiler
    收藏夹路径设置
    MS SQL入门基础:数据转换服务基本概念
    MS SQL入门基础:复制的概述和术语
    MS SQL入门基础:创建备份设备
    巧用ASP生成PDF文件
    MS SQL入门基础:sql 警报
    MS SQL入门基础:基于HTTP的数据访问
    MS SQL入门基础:XML文档与数据库表
    MS SQL入门基础:复制监视器
  • 原文地址:https://www.cnblogs.com/xymqx/p/3403837.html
Copyright © 2011-2022 走看看