精华内容
下载资源
问答
  • Semaphore允许您将许可增加到超出初始数量的范围。这可以用于例如节流。...通过在应用程序中编程约定,可以正确使用信号量。 换一种说法: semaphore.release(10),将在当前许可的基础上再增加10个许可 ..

    Semaphore允许您将许可增加到超出初始数量的范围。这可以用于例如节流。acquire()/release()根据某些逻辑,工作线程将执行此操作,并且节流器将减少/增加许可。

     

    release()方法用于增加许可,因为从文档开始:

    不要求释放许可证的线程必须通过调用acquire获取该许可证。通过在应用程序中编程约定,可以正确使用信号量。

    换一种说法:

    semaphore.release(10),将在当前许可的基础上再增加10个许可

     

    release()如果该线程先前未从该线程获得许可,则可以将其视为“创建”另一个许可Semaphore

    展开全文
  • 初始化一个信号量:Semaphore hSemaphore = new Semaphore...如果是主线程执行Release(3),不就是把信号量记数增加到3嘛,难道说成“退出3次信号量”?主线程没有进入信号量啊,谈何退出呢? 该咋解释Release方法啊?
  • 关于信号量Semaphore的acquire与release的说明 1、Semaphore信号量作为一种流控手段,可以对特定资源的允许同时访问的操作数量进行控制,例如池化技术(连接池)中的并发数,有界阻塞容器的容量等。 2、Semaphore...

    Semaphore 是 synchronized 的加强版,作用是控制线程的并发数量。

    关于信号量Semaphore的acquire与release的说明

    1、Semaphore信号量作为一种流控手段,可以对特定资源的允许同时访问的操作数量进行控制,例如池化技术(连接池)中的并发数,有界阻塞容器的容量等。

     

    2、Semaphore中包含初始化时固定个数的许可,在进行操作的时候,需要先acquire获取到许可,才可以继续执行任务,如果获取失败,则进入阻塞;处理完成之后需要release释放许可。

     

    3、acquire与release之间的关系:在实现中不包含真正的许可对象,并且Semaphore也不会将许可与线程关联起来,因此在一个线程中获得的许可可以在另一个线程中释放。可以将acquire操作视为是消费一个许可,而release操作是创建一个许可,Semaphore并不受限于它在创建时的初始许可数量。也就是说acquire与release并没有强制的一对一关系,release一次就相当于新增一个许可,许可的数量可能会由于没有与acquire操作一对一而导致超出初始化时设置的许可个数。

    展开全文
  • 本系列文章分两部分,第一部分详细地介绍了 Linux 内核中的同步机制:原子操作、信号量、读写信号量和自旋锁的API,使用要求以及一些典型示例。第二部分将详细介绍在Linux内核中的另外一些同步机制,包括大内核锁、...

    本系列文章分两部分,第一部分详细地介绍了 Linux 内核中的同步机制:原子操作、信号量、读写信号量和自旋锁的API,使用要求以及一些典型示例。第二部分将详细介绍在Linux内核中的另外一些同步机制,包括大内核锁、读写锁、大读者锁、RCU和顺序锁。

    一、 引言


    在现代操作系统里,同一时间可能有多个内核执行流在执行,因此内核其实象多进程多线程编程一样也需要一些同步机制来同步各执行单元对共享数据的访 问。尤其是在多处理器系统上,更需要一些同步机制来同步不同处理器上的执行单元对共享的数据的访问。在主流的Linux内核中包含了几乎所有现代的操作系 统具有的同步机制,这些同步机制包括:原子操作、信号量(semaphore)、读写信号量(rw_semaphore)、spinlock、 BKL(Big Kernel Lock)、rwlock、brlock(只包含在2.4内核中)、RCU(只包含在2.6内核中)和seqlock(只包含在2.6内核中)。


    本文的下面各章节将详细讲述每一种同步机制的原理、用途、API以及典型应用示例。


    二、原子操作


    所谓原子操作,就是该操作绝不会在执行完毕前被任何其他任务或事件打断,也就说,它的最小的执行单位,不可能有比它更小的执行单位,因此这里的原子实际是使用了物理学里的物质微粒的概念。


    原子操作需要硬件的支持,因此是架构相关的,其API和原子类型的定义都定义在内核源码树的include/asm/atomic.h文件中,它们都使用汇编语言实现,因为C语言并不能实现这样的操作。


    原子操作主要用于实现资源计数,很多引用计数(refcnt)就是通过原子操作实现的。


    原子类型定义如下:




    typedef struct { volatile int counter; } atomic_t;


    volatile修饰字段告诉gcc不要对该类型的数据做优化处理,对它的访问都是对内存的访问,而不是对寄存器的访问。


    原子操作API包括:




    atomic_read(atomic_t * v);


    该函数对原子类型的变量进行原子读操作,它返回原子类型的变量v的值。




    atomic_set(atomic_t * v, int i);


    该函数设置原子类型的变量v的值为i。




    void atomic_add(int i, atomic_t *v);


    该函数给原子类型的变量v增加值i。




    atomic_sub(int i, atomic_t *v);


    该函数从原子类型的变量v中减去i。




    int atomic_sub_and_test(int i, atomic_t *v);


    该函数从原子类型的变量v中减去i,并判断结果是否为0,如果为0,返回真,否则返回假。




    void atomic_inc(atomic_t *v);


    该函数对原子类型变量v原子地增加1。




    void atomic_dec(atomic_t *v);


    该函数对原子类型的变量v原子地减1。




    int atomic_dec_and_test(atomic_t *v);


    该函数对原子类型的变量v原子地减1,并判断结果是否为0,如果为0,返回真,否则返回假。




    int atomic_inc_and_test(atomic_t *v);


    该函数对原子类型的变量v原子地增加1,并判断结果是否为0,如果为0,返回真,否则返回假。




    int atomic_add_negative(int i, atomic_t *v);


    该函数对原子类型的变量v原子地增加I,并判断结果是否为负数,如果是,返回真,否则返回假。




    int atomic_add_return(int i, atomic_t *v);


    该函数对原子类型的变量v原子地增加i,并且返回指向v的指针。




    int atomic_sub_return(int i, atomic_t *v);


    该函数从原子类型的变量v中减去i,并且返回指向v的指针。




    int atomic_inc_return(atomic_t * v);


    该函数对原子类型的变量v原子地增加1并且返回指向v的指针。




    int atomic_dec_return(atomic_t * v);


    该函数对原子类型的变量v原子地减1并且返回指向v的指针。


    原子操作通常用于实现资源的引用计数,在TCP/IP协议栈的IP碎片处理中,就使用了引用计数,碎片队列结构struct ipq描述了一个IP碎片,字段refcnt就是引用计数器,它的类型为atomic_t,当创建IP碎片时(在函数ip_frag_create中), 使用atomic_set函数把它设置为1,当引用该IP碎片时,就使用函数atomic_inc把引用计数加1,当不需要引用该IP碎片时,就使用函数 ipq_put来释放该IP碎片,ipq_put使用函数atomic_dec_and_test把引用计数减1并判断引用计数是否为0,如果是就释放 IP碎片。函数ipq_kill把IP碎片从ipq队列中删除,并把该删除的IP碎片的引用计数减1(通过使用函数atomic_dec实现)。




    三、信号量(semaphore)


    Linux内核的信号量在概念和原理上与用户态的System V的IPC机制信号量是一样的,但是它绝不可能在内核之外使用,因此它与System V的IPC机制信号量毫不相干。


    信号量在创建时需要设置一个初始值,表示同时可以有几个任务可以访问该信号量保护的共享资源,初始值为1就变成互斥锁(Mutex),即同时只能有 一个任务可以访问信号量保护的共享资源。一个任务要想访问共享资源,首先必须得到信号量,获取信号量的操作将把信号量的值减1,若当前信号量的值为负数, 表明无法获得信号量,该任务必须挂起在该信号量的等待队列等待该信号量可用;若当前信号量的值为非负数,表示可以获得信号量,因而可以立刻访问被该信号量 保护的共享资源。当任务访问完被信号量保护的共享资源后,必须释放信号量,释放信号量通过把信号量的值加1实现,如果信号量的值为非正数,表明有任务等待 当前信号量,因此它也唤醒所有等待该信号量的任务。


    信号量的API有:




    DECLARE_MUTEX(name)


    该宏声明一个信号量name并初始化它的值为0,即声明一个互斥锁。




    DECLARE_MUTEX_LOCKED(name)


    该宏声明一个互斥锁name,但把它的初始值设置为0,即锁在创建时就处在已锁状态。因此对于这种锁,一般是先释放后获得。




    void sema_init (struct semaphore *sem, int val);


    该函用于数初始化设置信号量的初值,它设置信号量sem的值为val。




    void init_MUTEX (struct semaphore *sem);


    该函数用于初始化一个互斥锁,即它把信号量sem的值设置为1。




    void init_MUTEX_LOCKED (struct semaphore *sem);


    该函数也用于初始化一个互斥锁,但它把信号量sem的值设置为0,即一开始就处在已锁状态。




    void down(struct semaphore * sem);


    该函数用于获得信号量sem,它会导致睡眠,因此不能在中断上下文(包括IRQ上下文和softirq上下文)使用该函数。该函数将把sem的值减1,如果信号量sem的值非负,就直接返回,否则调用者将被挂起,直到别的任务释放该信号量才能继续运行。




    int down_interruptible(struct semaphore * sem);


    该函数功能与down类似,不同之处为,down不会被信号(signal)打断,但down_interruptible能被信号打断,因此该函数有返回值来区分是正常返回还是被信号中断,如果返回0,表示获得信号量正常返回,如果被信号打断,返回-EINTR。




    int down_trylock(struct semaphore * sem);


    该函数试着获得信号量sem,如果能够立刻获得,它就获得该信号量并返回0,否则,表示不能获得信号量sem,返回值为非0值。因此,它不会导致调用者睡眠,可以在中断上下文使用。




    void up(struct semaphore * sem);


    该函数释放信号量sem,即把sem的值加1,如果sem的值为非正数,表明有任务等待该信号量,因此唤醒这些等待者。


    信号量在绝大部分情况下作为互斥锁使用,下面以console驱动系统为例说明信号量的使用。


    在内核源码树的kernel/printk.c中,使用宏DECLARE_MUTEX声明了一个互斥锁console_sem,它用于保护 console驱动列表console_drivers以及同步对整个console驱动系统的访问,其中定义了函数 acquire_console_sem来获得互斥锁console_sem,定义了release_console_sem来释放互斥锁 console_sem,定义了函数try_acquire_console_sem来尽力得到互斥锁console_sem。这三个函数实际上是分别对 函数down,up和down_trylock的简单包装。需要访问console_drivers驱动列表时就需要使用 acquire_console_sem来保护console_drivers列表,当访问完该列表后,就调用release_console_sem释 放信号量console_sem。函数 console_unblank,console_device,console_stop,console_start,register_console 和unregister_console都需要访问console_drivers,因此它们都使用函数对acquire_console_sem和 release_console_sem来对console_drivers进行保护。




    四、读写信号量(rw_semaphore)


    读写信号量对访问者进行了细分,或者为读者,或者为写者,读者在保持读写信号量期间只能对该读写信号量保护的共享资源进行读访问,如果一个任务除了 需要读,可能还需要写,那么它必须被归类为写者,它在对共享资源访问之前必须先获得写者身份,写者在发现自己不需要写访问的情况下可以降级为读者。读写信 号量同时拥有的读者数不受限制,也就说可以有任意多个读者同时拥有一个读写信号量。如果一个读写信号量当前没有被写者拥有并且也没有写者等待读者释放信号 量,那么任何读者都可以成功获得该读写信号量;否则,读者必须被挂起直到写者释放该信号量。如果一个读写信号量当前没有被读者或写者拥有并且也没有写者等 待该信号量,那么一个写者可以成功获得该读写信号量,否则写者将被挂起,直到没有任何访问者。因此,写者是排他性的,独占性的。


    读写信号量有两种实现,一种是通用的,不依赖于硬件架构,因此,增加新的架构不需要重新实现它,但缺点是性能低,获得和释放读写信号量的开销大;另 一种是架构相关的,因此性能高,获取和释放读写信号量的开销小,但增加新的架构需要重新实现。在内核配置时,可以通过选项去控制使用哪一种实现。


    读写信号量的相关API有:




    DECLARE_RWSEM(name)


    该宏声明一个读写信号量name并对其进行初始化。




    void init_rwsem(struct rw_semaphore *sem);


    该函数对读写信号量sem进行初始化。




    void down_read(struct rw_semaphore *sem);


    读者调用该函数来得到读写信号量sem。该函数会导致调用者睡眠,因此只能在进程上下文使用。




    int down_read_trylock(struct rw_semaphore *sem);


    该函数类似于down_read,只是它不会导致调用者睡眠。它尽力得到读写信号量sem,如果能够立即得到,它就得到该读写信号量,并且返回1,否则表示不能立刻得到该信号量,返回0。因此,它也可以在中断上下文使用。




    void down_write(struct rw_semaphore *sem);


    写者使用该函数来得到读写信号量sem,它也会导致调用者睡眠,因此只能在进程上下文使用。




    int down_write_trylock(struct rw_semaphore *sem);


    该函数类似于down_write,只是它不会导致调用者睡眠。该函数尽力得到读写信号量,如果能够立刻获得,就获得该读写信号量并且返回1,否则表示无法立刻获得,返回0。它可以在中断上下文使用。




    void up_read(struct rw_semaphore *sem);


    读者使用该函数释放读写信号量sem。它与down_read或down_read_trylock配对使用。如果down_read_trylock返回0,不需要调用up_read来释放读写信号量,因为根本就没有获得信号量。




    void up_write(struct rw_semaphore *sem);


    写者调用该函数释放信号量sem。它与down_write或down_write_trylock配对使用。如果down_write_trylock返回0,不需要调用up_write,因为返回0表示没有获得该读写信号量。




    void downgrade_write(struct rw_semaphore *sem);


    该函数用于把写者降级为读者,这有时是必要的。因为写者是排他性的,因此在写者保持读写信号量期间,任何读者或写者都将无法访问该读写信号量保护的 共享资源,对于那些当前条件下不需要写访问的写者,降级为读者将,使得等待访问的读者能够立刻访问,从而增加了并发性,提高了效率。


    读写信号量适于在读多写少的情况下使用,在linux内核中对进程的内存映像描述结构的访问就使用了读写信号量进行保护。在Linux中,每一个进 程都用一个类型为task_t或struct task_struct的结构来描述,该结构的类型为struct mm_struct的字段mm描述了进程的内存映像,特别是mm_struct结构的mmap字段维护了整个进程的内存块列表,该列表将在进程生存期间被 大量地遍利或修改,因此mm_struct结构就有一个字段mmap_sem来对mmap的访问进行保护,mmap_sem就是一个读写信号量,在 proc文件系统里有很多进程内存使用情况的接口,通过它们能够查看某一进程的内存使用情况,命令free、ps和top都是通过proc来得到内存使用 信息的,proc接口就使用down_read和up_read来读取进程的mmap信息。当进程动态地分配或释放内存时,需要修改mmap来反映分配或 释放后的内存映像,因此动态内存分配或释放操作需要以写者身份获得读写信号量mmap_sem来对mmap进行更新。系统调用brk和munmap就使用 了down_write和up_write来保护对mmap的访问。




    五、自旋锁(spinlock)
    自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释 放了锁,"自旋"一词就是因此而得名。由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁。


    信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因此只能在进程上下文使用(_trylock的变种能够在中断上下文使用), 而自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用。如果被保护的共享资源只在进程上下文访问,使用信号量保护该共享资源非常合适,如果对共巷 资源的访问时间非常短,自旋锁也可以。但是如果被保护的共享资源需要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁。


    自旋锁保持期间是抢占失效的,而信号量和读写信号量保持期间是可以被抢占的。自旋锁只有在内核可抢占或SMP的情况下才真正需要,在单CPU且不可抢占的内核下,自旋锁的所有操作都是空操作。


    跟互斥锁一样,一个执行单元要想访问被自旋锁保护的共享资源,必须先得到锁,在访问完共享资源后,必须释放锁。如果在获取自旋锁时,没有任何执行单 元保持该锁,那么将立即得到锁;如果在获取自旋锁时锁已经有保持者,那么获取锁操作将自旋在那里,直到该自旋锁的保持者释放了锁。


    无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。


    自旋锁的API有:




    spin_lock_init(x)
    http://www.ibm.com/developerworks/cn/linux/l-synch/part1/


    该宏用于初始化自旋锁x。自旋锁在真正使用前必须先初始化。该宏用于动态初始化。




    DEFINE_SPINLOCK(x)


    该宏声明一个自旋锁x并初始化它。该宏在2.6.11中第一次被定义,在先前的内核中并没有该宏。




    SPIN_LOCK_UNLOCKED


    该宏用于静态初始化一个自旋锁。




    DEFINE_SPINLOCK(x)等同于spinlock_t x = SPIN_LOCK_UNLOCKED
    spin_is_locked(x)


    该宏用于判断自旋锁x是否已经被某执行单元保持(即被锁),如果是,返回真,否则返回假。




    spin_unlock_wait(x)


    该宏用于等待自旋锁x变得没有被任何执行单元保持,如果没有任何执行单元保持该自旋锁,该宏立即返回,否则将循环在那里,直到该自旋锁被保持者释放。




    spin_trylock(lock)


    该宏尽力获得自旋锁lock,如果能立即获得锁,它获得锁并返回真,否则不能立即获得锁,立即返回假。它不会自旋等待lock被释放。




    spin_lock(lock)


    该宏用于获得自旋锁lock,如果能够立即获得锁,它就马上返回,否则,它将自旋在那里,直到该自旋锁的保持者释放,这时,它获得锁并返回。总之,只有它获得锁才返回。




    spin_lock_irqsave(lock, flags)


    该宏获得自旋锁的同时把标志寄存器的值保存到变量flags中并失效本地中断。




    spin_lock_irq(lock)


    该宏类似于spin_lock_irqsave,只是该宏不保存标志寄存器的值。




    spin_lock_bh(lock)


    该宏在得到自旋锁的同时失效本地软中断。




    spin_unlock(lock)


    该宏释放自旋锁lock,它与spin_trylock或spin_lock配对使用。如果spin_trylock返回假,表明没有获得自旋锁,因此不必使用spin_unlock释放。




    spin_unlock_irqrestore(lock, flags)


    该宏释放自旋锁lock的同时,也恢复标志寄存器的值为变量flags保存的值。它与spin_lock_irqsave配对使用。




    spin_unlock_irq(lock)


    该宏释放自旋锁lock的同时,也使能本地中断。它与spin_lock_irq配对应用。




    spin_unlock_bh(lock)


    该宏释放自旋锁lock的同时,也使能本地的软中断。它与spin_lock_bh配对使用。




    spin_trylock_irqsave(lock, flags)


    该宏如果获得自旋锁lock,它也将保存标志寄存器的值到变量flags中,并且失效本地中断,如果没有获得锁,它什么也不做。因此如果能够立即获 得锁,它等同于spin_lock_irqsave,如果不能获得锁,它等同于spin_trylock。如果该宏获得自旋锁lock,那需要使用 spin_unlock_irqrestore来释放。




    spin_trylock_irq(lock)


    该宏类似于spin_trylock_irqsave,只是该宏不保存标志寄存器。如果该宏获得自旋锁lock,需要使用spin_unlock_irq来释放。




    spin_trylock_bh(lock)


    该宏如果获得了自旋锁,它也将失效本地软中断。如果得不到锁,它什么也不做。因此,如果得到了锁,它等同于spin_lock_bh,如果得不到锁,它等同于spin_trylock。如果该宏得到了自旋锁,需要使用spin_unlock_bh来释放。




    spin_can_lock(lock)


    该宏用于判断自旋锁lock是否能够被锁,它实际是spin_is_locked取反。如果lock没有被锁,它返回真,否则,返回假。该宏在2.6.11中第一次被定义,在先前的内核中并没有该宏。


    获得自旋锁和释放自旋锁有好几个版本,因此让读者知道在什么样的情况下使用什么版本的获得和释放锁的宏是非常必要的。


    如果被保护的共享资源只在进程上下文访问和软中断上下文访问,那么当在进程上下文访问共享资源时,可能被软中断打断,从而可能进入软中断上下文来对 被保护的共享资源访问,因此对于这种情况,对共享资源的访问必须使用spin_lock_bh和spin_unlock_bh来保护。当然使用 spin_lock_irq和spin_unlock_irq以及spin_lock_irqsave和spin_unlock_irqrestore也 可以,它们失效了本地硬中断,失效硬中断隐式地也失效了软中断。但是使用spin_lock_bh和spin_unlock_bh是最恰当的,它比其他两 个快。


    如果被保护的共享资源只在进程上下文和tasklet或timer上下文访问,那么应该使用与上面情况相同的获得和释放锁的宏,因为tasklet和timer是用软中断实现的。


    如果被保护的共享资源只在一个tasklet或timer上下文访问,那么不需要任何自旋锁保护,因为同一个tasklet或timer只能在一个 CPU上运行,即使是在SMP环境下也是如此。实际上tasklet在调用tasklet_schedule标记其需要被调度时已经把该tasklet绑 定到当前CPU,因此同一个tasklet决不可能同时在其他CPU上运行。timer也是在其被使用add_timer添加到timer队列中时已经被 帮定到当前CPU,所以同一个timer绝不可能运行在其他CPU上。当然同一个tasklet有两个实例同时运行在同一个CPU就更不可能了。


    如果被保护的共享资源只在两个或多个tasklet或timer上下文访问,那么对共享资源的访问仅需要用spin_lock和 spin_unlock来保护,不必使用_bh版本,因为当tasklet或timer运行时,不可能有其他tasklet或timer在当前CPU上运 行。 如果被保护的共享资源只在一个软中断(tasklet和timer除外)上下文访问,那么这个共享资源需要用spin_lock和spin_unlock 来保护,因为同样的软中断可以同时在不同的CPU上运行。


    如果被保护的共享资源在两个或多个软中断上下文访问,那么这个共享资源当然更需要用spin_lock和spin_unlock来保护,不同的软中断能够同时在不同的CPU上运行。


    如果被保护的共享资源在软中断(包括tasklet和timer)或进程上下文和硬中断上下文访问,那么在软中断或进程上下文访问期间,可能被硬中 断打断,从而进入硬中断上下文对共享资源进行访问,因此,在进程或软中断上下文需要使用spin_lock_irq和spin_unlock_irq来保 护对共享资源的访问。而在中断处理句柄中使用什么版本,需依情况而定,如果只有一个中断处理句柄访问该共享资源,那么在中断处理句柄中仅需要 spin_lock和spin_unlock来保护对共享资源的访问就可以了。因为在执行中断处理句柄期间,不可能被同一CPU上的软中断或进程打断。但 是如果有不同的中断处理句柄访问该共享资源,那么需要在中断处理句柄中使用spin_lock_irq和spin_unlock_irq来保护对共享资源 的访问。


    在使用spin_lock_irq和spin_unlock_irq的情况下,完全可以用spin_lock_irqsave和 spin_unlock_irqrestore取代,那具体应该使用哪一个也需要依情况而定,如果可以确信在对共享资源访问前中断是使能的,那么使用 spin_lock_irq更好一些,因为它比spin_lock_irqsave要快一些,但是如果你不能确定是否中断使能,那么使用 spin_lock_irqsave和spin_unlock_irqrestore更好,因为它将恢复访问共享资源前的中断标志而不是直接使能中断。当 然,有些情况下需要在访问共享资源时必须中断失效,而访问完后必须中断使能,这样的情形使用spin_lock_irq和spin_unlock_irq 最好。


    需要特别提醒读者,spin_lock用于阻止在不同CPU上的执行单元对共享资源的同时访问以及不同进程上下文互相抢占导致的对共享资源的非同步访问,而中断失效和软中断失效却是为了阻止在同一CPU上软中断或中断对共享资源的非同步访问。


    本系列文章的第二部分将详细介绍Linux内核中的其他一些同步机制,包括大内核锁、读写锁、大读者锁、RCU和顺序锁。

    展开全文
  • 信号量(Semaphore)是由内核对象维护的int变量,当信号量为0时,在信号量上等待的线程会堵塞,信号量大于0时,就解除堵塞。当在一个信号量上等待的线程解除堵塞时,内核自动会将信号量的计数减1。在.net 下通过...
  • 翻译:GentlemanTsao,2020-06-08; 简单的信号量; 使用信号量传递信号; 计数信号量; 有界信号量; 将信号量用作锁

    翻译:GentlemanTsao,2020-06-08


    信号量是一种线程同步结构,可用于在线程之间发送信号以避免信号丢失,或像使用锁一样保护临界区。 Java 5在java.util.concurrent包中附带了信号量实现,因此你不必实现自己的信号量。 尽管如此,了解其实现和使用背后的理论还是很有用的。

    简单的信号量

    如下是一个简单的信号量实现:

    public class Semaphore {
      private boolean signal = false;
    
      public synchronized void take() {
        this.signal = true;
        this.notify();
      }
    
      public synchronized void release() throws InterruptedException{
        while(!this.signal) wait();
        this.signal = false;
      }
    
    }

    take()方法发送一个信号,该信号存储在信号量内部。 release()方法等待信号。 收到信号标志后,将再次将其清除,并退出release()方法。

    使用这样的信号量可以避免信号丢失。 用take()代替notify(),以及用release()代替wait()。 如果对take()的调用发生在对release()的调用之前,则调用release()的线程仍会知道take()被调用了,因为信号存储在内部的signal变量中。 使用wait()和notify()不会存储信号。

    当使用信号量进行信号传递时,方法名take()和release()似乎有点奇怪。 该名称源于信号量作为锁来使用,如本文后面所述。 在这种情况下,该方法名更有意义。

    使用信号量传递信号

    如下是一个简化示例,两个线程使用信号量互相发信号:

    Semaphore semaphore = new Semaphore();
    
    SendingThread sender = new SendingThread(semaphore);
    
    ReceivingThread receiver = new ReceivingThread(semaphore);
    
    receiver.start();
    sender.start();
    public class SendingThread {
      Semaphore semaphore = null;
    
      public SendingThread(Semaphore semaphore){
        this.semaphore = semaphore;
      }
    
      public void run(){
        while(true){
          //do something, then signal
          this.semaphore.take();
    
        }
      }
    }
    public class RecevingThread {
      Semaphore semaphore = null;
    
      public ReceivingThread(Semaphore semaphore){
        this.semaphore = semaphore;
      }
    
      public void run(){
        while(true){
          this.semaphore.release();
          //receive signal, then do something...
        }
      }
    }

    计数信号量

    上一节中的Semaphore实现不计算take()方法发送信号的次数。 我们可以更改Semaphore来做到这一点。 这称为计数信号量。 如下是计数信号量的简单实现:

    public class CountingSemaphore {
      private int signals = 0;
    
      public synchronized void take() {
        this.signals++;
        this.notify();
      }
    
      public synchronized void release() throws InterruptedException{
        while(this.signals == 0) wait();
        this.signals--;
      }
    
    }

    有界信号量

    CoutingSemaphore没有存储信号数量的上限。 我们可以将信号量的实现更改为有上限的,如下所示:

    public class BoundedSemaphore {
      private int signals = 0;
      private int bound   = 0;
    
      public BoundedSemaphore(int upperBound){
        this.bound = upperBound;
      }
    
      public synchronized void take() throws InterruptedException{
        while(this.signals == bound) wait();
        this.signals++;
        this.notify();
      }
    
      public synchronized void release() throws InterruptedException{
        while(this.signals == 0) wait();
        this.signals--;
        this.notify();
      }
    }

    请注意,如果信号数量等于上限,则take()方法现在会阻塞。 如果BoundedSemaphore已达到其信号上限,则调用take()的线程不再允许传递其信号,直到某个线程调用release()。

    将信号量用作锁

    可以将有界信号量用作锁。 为此,请将上限设置为1,并调用take()和release()来保护临界区。 如下是一个例子:

    BoundedSemaphore semaphore = new BoundedSemaphore(1);
    
    ...
    
    semaphore.take();
    
    try{
      //critical section
    } finally {
      semaphore.release();
    }

    与使用信号相比,方法take()和release()现在由同一线程调用。 由于只允许一个线程使用信号量,因此所有调用take()的其他线程都将被阻塞,直到调用release()为止。 由于总是先调用take(),因此release()的调用永远不会阻塞。

    你还可以使用有界信号量来限制代码段中允许的线程数。 例如,在上面的示例中,如果将BoundedSemaphore的限制设置为5,情况会怎么样呢? 那样的话,一次允许5个线程进入临界区。 但是,你必须确保这5个线程的线程操作不会冲突,否则应用程序将出错。

    从finally块内部调用release()方法以确保即使从临界区抛出异常也能被调用到。

    下一篇:
    2020版Java并发和多线程教程(二十五):阻塞队列

    并发系列专栏:
    Java并发和多线程教程2020版

    展开全文
  • 操作系统之信号量

    万次阅读 多人点赞 2018-03-29 23:13:32
    操作系统中的信号量在解决线程之间的同步中起着非常大的作用,那么什么是信号量呢?百度百科:信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发...
  • python信号量的使用
  • 信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。...对信号量有两种操作,take 和 release。  程序中,首先初始化信号量为0,...
  • QSemaphore 信号量

    2017-04-10 11:03:10
    QSemaphore类提供了一种通用的计数信号量信号量是互斥锁的泛化。一个互斥锁只能被锁住一次,而一个信号量可以被多次请求。所以,信号量通常用来保护某个特定数量的资源。
  • 异步信号量 基于 Java 的并发信号量的计数信号量。 安装 通过 npm 安装模块: npm install async-semaphore 快速示例 // fairness false var Semaphore = require ( 'async-semaphore' ) ; var semaphore = new ...
  • Semaphore信号量

    2019-05-23 01:20:36
    //没有获取到信号量返回 return; } try{ //doSomething(); }finally{ semaphore.release(); } 上线的时候没有写semaphore.release();这句,导致预先设定好的量用完之后再也进不到...
  • 信号量

    2015-12-14 16:55:15
     信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施, 它负责协调各个线程, 以保证它们能够正确、合理的使用公共资源。  一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在...
  • 一、信号量概述 Redisson的分布式信号量(Semaphore)Java对象 RSemaphore 采用了与 java.util.concurrent.Semaphore 相似的接口和用法。同时还提供了异步(Async)、反射式(Reactive)和RxJava2标准的接口。 1.1 ...
  • 信号量: P/V原语 P:如果信号量>0,递减信号量,操作继续,否则睡眠等待信号量>0 V:递增信号量,若递增的信号量>0,则唤醒进程 /* Please don't access any members of this structure directly */ struct ...
  • 信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。...为了完成这个过程,需要创建一个信号量VI,然后将Acquire Semaphore VI以及Release S...
  • 信号量 在go语言上实现几种信号量模式。 例子 基本信号量 BasicSemaphore是一个管理固定数量的并发任务的信号灯。 func TestSemaphore ( t * testing. T ) { permit := 3 sem := semaphore . NewSemaphore ( ...
  • 二值信号量和互斥信号量的区别

    千次阅读 2014-06-30 16:00:23
    互斥信号量和二进制信号量的区别   互斥型信号量必须是同一个任务申请,同一个任务释放,其他任务释放无效。同一个任务可以递归申请。    二进制信号量,一个任务申请成功后,可以由另一个任务释放。  ...
  • 互斥信号量和二进制信号量的区别(转)2009年12月03日 星期四 09:50 A.M.互斥信号量和二进制信号量的区别 互斥型信号量必须是同一个任务申请,同一个任务释放,其他任务释放无效。同一个任务可以递归申请。 二进制...
  • 信号量Semaphore初探

    千次阅读 2016-09-21 13:06:28
    信号量Semaphore是java.util.concurrent包下一个常用的同步工具类,它维护了一个许可集,可以理解成资源数,可以通过acquire操作来获取一个资源,并通过release来释放一个资源,但需要注意的是,release来释放资源前...
  • 信号量与信号

    2012-05-10 14:57:39
    信号量(Semaphore),有时被称为信号灯,是在多环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。...为了完成这个过程,需要创建一个信号量VI,然后将Acquire Semaphore VI以及Release Sem
  • Java信号量Semaphor解析

    2017-05-08 10:42:16
    我们应当知道,信号量代表的是一种有限的访问资源。在多线程的并发操作中,信号量在共享资源的控制中有着很重要的作用。Java中提供的Semaphor类可以对信号量进行操作,调用acquire()方法获取一个许可,如果没有则...
  • Python 信号量

    2019-01-26 14:17:28
    import threading,time class myThread(threading.Thread): def run(self): if semaphore.acquire(): print(self.name) time.sleep(3) semaphore.release() i...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 54,678
精华内容 21,871
关键字:

信号量release