首页 文章

什么是信号量?

提问于
浏览
288

信号量是一种经常用于解决多线程问题的编程概念 . 我向社区提出的问题:

什么是信号量,你如何使用它?

13 回答

  • 12

    把信号量想象成夜总会的保镖 . 俱乐部一次允许有一定数量的人 . 如果俱乐部已经满员,则不允许任何人进入,但只要一个人离开另一个人就可以进入 .

    它只是一种限制特定资源的消费者数量的方法 . 例如,限制应用程序中对数据库的同时调用次数 .

    这是C#中一个非常教学的例子:-)

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Threading;
    
    namespace TheNightclub
    {
        public class Program
        {
            public static Semaphore Bouncer { get; set; }
    
            public static void Main(string[] args)
            {
                // Create the semaphore with 3 slots, where 3 are available.
                Bouncer = new Semaphore(3, 3);
    
                // Open the nightclub.
                OpenNightclub();
            }
    
            public static void OpenNightclub()
            {
                for (int i = 1; i <= 50; i++)
                {
                    // Let each guest enter on an own thread.
                    Thread thread = new Thread(new ParameterizedThreadStart(Guest));
                    thread.Start(i);
                }
            }
    
            public static void Guest(object args)
            {
                // Wait to enter the nightclub (a semaphore to be released).
                Console.WriteLine("Guest {0} is waiting to entering nightclub.", args);
                Bouncer.WaitOne();          
    
                // Do some dancing.
                Console.WriteLine("Guest {0} is doing some dancing.", args);
                Thread.Sleep(500);
    
                // Let one guest out (release one semaphore).
                Console.WriteLine("Guest {0} is leaving the nightclub.", args);
                Bouncer.Release(1);
            }
        }
    }
    
  • 1

    Michael Barr撰写的文章Mutexes and Semaphores Demystified是对互斥体和信号量不同的简短介绍,以及它们应该和不应该使用的时间 . 我在这里摘录了几个关键段落 .

    关键是应该使用互斥锁来保护共享资源,而信号量应该用于信令 . 您通常不应使用信号量来保护共享资源,也不应使用互斥信号来保护信令 . 例如,在使用信号量来保护共享资源方面,有一些问题与保镖类比 - 你可以这样使用它们,但它可能导致难以诊断错误 .

    虽然互斥锁和信号量在实现方面有一些相似之处,但它们应始终以不同的方式使用 . 对顶部提出的问题最常见(但仍然不正确)的答案是互斥体和信号量非常相似,唯一的显着差异是信号量可以高于1 . 几乎所有工程师似乎都能正确理解互斥锁是一个二进制标志,用于通过确保代码的关键部分内的互斥来保护共享资源 . 但是当被要求扩展如何使用“计数信号量”时,大多数工程师 - 仅仅在他们的信任程度上有所不同 - 表达了教科书意见的一些风格,这些用于保护几个等价资源 .

    ...

    在这一点上,一个有趣的类比是使用浴室钥匙的概念作为保护共享资源 - 浴室 . 如果商店有一个单独的浴室,那么一把钥匙就足以保护该资源并防止多人同时使用它 .

    如果有多个浴室,人们可能会想要对它们进行相似的键并制作多个键 - 这类似于信号量被误用 . 一旦你有一把钥匙,你实际上并不知道哪个浴室可用,如果沿着这条路走下去,你可能最终会使用互斥体来提供这些信息,并确保你没有带一个已经被占用的浴室 .

    信号量是保护几个基本相同资源的错误工具,但这是有多少人想到它并使用它 . 保镖的类比明显不同 - 没有几种相同类型的资源,而是有一种资源可以接受多个同时使用的用户 . 我认为在这种情况下可以使用信号量,但很少有真实情况下类比实际存在 - 更常见的是有几种相同类型,但仍然是个别资源,如浴室,不能使用这条路 .

    ...

    正确使用信号量是为了从一个任务发信号到另一个任务 . 互斥体意味着每个使用其保护的共享资源的任务始终按此顺序获取和释放 . 相比之下,使用信号量的任务要么发信号要么等待,而不是两者 . 例如,任务1可以包含当按下“电源”按钮时发布(即,发信号或递增)特定信号量的代码,并且唤醒显示的任务2挂在同一个信号量上 . 在这种情况下,一个任务是事件信号的 生产环境 者;另一个是消费者 .

    ...

    这里有一个重要的观点,即互斥体以一种糟糕的方式干扰实时操作系统,导致优先级倒置,其中由于资源共享,可能在更重要的任务之前执行不太重要的任务 . 简而言之,当较低优先级的任务使用互斥锁来获取资源A时,会发生这种情况,然后尝试抓取B,但由于B不可用而暂停 . 当它正在等待时,一个更高优先级的任务出现并且需要A,但是它已经被捆绑了,并且由于它正在等待B而没有运行的进程 . 有很多方法可以解决这个问题,但它通常是修复的通过改变互斥锁和任务管理器 . 在这些情况下互斥体比二进制信号量复杂得多,并且在这样的实例中使用信号量将导致优先级反转,因为任务管理器不知道优先级倒置并且不能用于纠正它 .

    ...

    互斥体和信号量之间广泛的现代混淆的原因是历史性的,因为它可以追溯到1974年由Djikstra发明的Semaphore(资本“S”,在本文中) . 在那个日期之前,没有任何中断安全的任务计算机科学家已知的同步和信令机制可以有效地扩展,以供两个以上的任务使用 . Dijkstra革命性的,安全可扩展的Semaphore应用于关键部分保护和信令 . 因此混乱开始了 . 然而,在基于优先级的抢占式RTOS(例如,VRTX,大约1980年)出现之后,操作系统开发人员显而易见,发布了 Build RMA的学术论文和优先级倒置引起的问题,以及关于优先级的论文继承协议在1990年,3显而易见,互斥量必须不仅仅是具有二进制计数器的信号量 .

    互斥:资源共享

    信号量:信令

    如果不仔细考虑副作用,请不要将其中一种用于另一种 .

  • 10

    Mutex:独占成员访问资源

    信号量:n成员访问资源

    也就是说,互斥锁可用于同步对计数器,文件,数据库等的访问 .

    sempahore可以做同样的事情,但支持固定数量的同时呼叫者 . 例如,我可以将数据库调用包装在信号量(3)中,这样我的多线程应用程序将以最多3个同时连接命中数据库 . 所有尝试都将阻止,直到三个插槽中的一个打开 . 他们做的事情就像做天真的节流真的很容易 .

  • -2

    @Craig:

    信号量是一种锁定资源的方法,以确保在执行一段代码时,只有这段代码才能访问该资源 . 这使得两个线程不会同时访问资源,这可能会导致问题 .

    这不仅限于一个线程 . 信号量可以配置为允许固定数量的线程访问资源 .

  • 63

    信号量也可以用作...信号量 . 例如,如果有多个进程将数据排入队列,并且只有一个任务从队列中消耗数据 . 如果您不希望您的使用任务不断轮询队列中的可用数据,则可以使用信号量 .

    这里信号量不是用作排除机制,而是用作信令机制 . 消耗任务正在等待信号量生成任务在信号量上发布 .

    这样,当且仅当有数据要出列时,消耗任务才会运行

  • 155

    构建并发程序有两个基本概念 - 同步和互斥 . 我们将看到这两种类型的锁(信号量通常是一种锁定机制)如何帮助我们实现同步和互斥 .

    信号量是一种编程结构,它通过实现同步和互斥来帮助我们实现并发 . 信号量有两种类型,Binary和Counting .

    信号量有两部分:计数器和等待访问特定资源的任务列表 . 信号量执行两个操作:wait(P)[这就像获取锁定],释放(V)[类似于释放锁定] - 这是人们可以对信号量执行的唯一两个操作 . 在二进制信号量中,计数器逻辑上介于0和1之间 . 您可以将其视为类似于具有两个值的锁:打开/关闭 . 计数信号量具有多个计数值 .

    重要的是要理解信号量计数器跟踪不必阻止的任务数量,即它们可以取得进展 . 任务阻止,仅在计数器为零时将自己添加到信号量列表中 . 因此,如果任务无法进行,则会将任务添加到P()例程的列表中,并使用V()例程“释放” .

    现在,很明显看到二进制信号量如何用于解决同步和互斥 - 它们本质上是锁 .

    恩 . 同步:

    thread A{
    semaphore &s; //locks/semaphores are passed by reference! think about why this is so.
    A(semaphore &s): s(s){} //constructor
    foo(){
    ...
    s.P();
    ;// some block of code B2
    ...
    }
    
    //thread B{
    semaphore &s;
    B(semaphore &s): s(s){} //constructor
    foo(){
    ...
    ...
    // some block of code B1
    s.V();
    ..
    }
    
    main(){
    semaphore s(0); // we start the semaphore at 0 (closed)
    A a(s);
    B b(s);
    }
    

    在上面的例子中,B2只能在B1完成执行后执行 . 假设线程A首先执行 - 获取sem.P(),并等待,因为计数器为0(关闭) . 线程B出现,完成B1,然后释放线程A - 然后完成B2 . 所以我们实现了同步 .

    现在让我们看看使用二进制信号量的互斥:

    thread mutual_ex{
    semaphore &s;
    mutual_ex(semaphore &s): s(s){} //constructor
    foo(){
    ...
    s.P();
    //critical section
    s.V();
    ...
    ...
    s.P();
    //critical section
    s.V();
    ...
    
    }
    
    main(){
    semaphore s(1);
    mutual_ex m1(s);
    mutual_ex m2(s);
    }
    

    互斥也很简单 - m1和m2不能同时进入临界区 . 因此,每个线程使用相同的信号量为其两个关键部分提供互斥 . 现在,是否有可能获得更大的并发性?取决于关键部分 . (想想如何使用信号量来实现互斥 . 暗示提示:我是否只需要使用一个信号量?)

    计数信号量:具有多个值的信号量 . 让我们来看看这意味着什么 - 一个具有多个值的锁?那么开放,封闭,......嗯 . 互斥或同步的多阶段锁定有什么用?

    让我们更容易两个:

    使用计数信号量进行同步:假设您有3个任务 - 您希望在3之后执行#1和2 . 您将如何设计同步?

    thread t1{
    ...
    s.P();
    //block of code B1
    
    thread t2{
    ...
    s.P();
    //block of code B2
    
    thread t3{
    ...
    //block of code B3
    s.V();
    s.V();
    }
    

    因此,如果您的信号量开始关闭,则确保t1和t2阻塞,并添加到信号量列表中 . 然后沿着所有重要的t3,完成其业务并释放t1和t2 . 他们被释放的顺序是什么?取决于信号量列表的实现 . 可以是FIFO,可以基于某些特定的优先级等 . (注意:考虑如何安排你的P和V;如果你想要以某种特定的顺序执行t1和t2,并且你不知道信号量的实现)

    (找出:如果V的数量大于P的数量,会发生什么?)

    相互排斥使用计数信号量:我希望你为此构建自己的伪代码(让你更好地理解事物!) - 但基本概念是这样的:计数器的计数信号量= N允许N个任务自由地进入临界区 . 这意味着你有N个任务(或线程,如果你愿意)进入关键部分,但是第N个任务被阻止(进入我们最喜欢的被阻止任务列表),只有当有人V是信号量时才通过至少一次 . 所以信号量计数器,而不是在0和1之间摆动,现在介于0和N之间,允许N个任务自由进入和退出,阻止任何人!

    天啊,为什么你需要这么蠢的东西?互不排斥的重点不是让不止一个人访问资源吗? (提示提示......你的计算机中并不总是只有一个驱动器,你......?)

    想一想:单独计算信号量是否可以实现互斥?如果您有10个资源实例,并且有10个线程进入(通过计数信号量)并尝试使用第一个实例,该怎么办?

  • 16

    考虑一下,可以容纳包括驾驶员在内的总共3(后)2(前)人的出租车 . 因此, semaphore 一次只允许5个人在车内 . 并且 mutex 只允许一个人坐在汽车的一个座位上 .

    因此, Mutex 允许资源(如OS线程)的独占访问,而 Semaphore 允许一次访问 n 资源 .

  • 1

    信号量是包含自然数(即,大于或等于零的整数)的对象,在该自然数上定义了两个修改操作 . 一项操作, V ,为自然增加了1 . 另一个操作 P 将自然数减1.这两个活动都是原子的(即没有其他操作可以与 VP 同时执行) .

    因为自然数0不能减少,所以在包含0的信号量上调用 P 将阻止调用进程(/ thread)的执行,直到某个时刻数字不再为0并且 P 可以成功(并且原子地)执行 .

    如其他答案中所述,信号量可用于将对特定资源的访问限制为最大(但可变)数量的进程 .

  • 348

    硬件或软件标志 . 在多任务系统中,信号量是变量,其值指示公共资源的状态 . 需要资源的过程检查信号量以确定资源状态,然后决定如何继续 .

  • 5

    所以想象一下每个人都想去洗手间,浴室里只有一定数量的钥匙 . 现在如果没有足够的钥匙,那个人需要等待 . 因此,将信号量视为代表可用于浴室的一组密钥(系统资源),不同的进程(浴室参与者)可以请求访问 .

    现在想象两个过程试图同时去洗手间 . 这不是一个好的情况,信号量用于防止这种情况 . 不幸的是,信号量是一种自愿的机制和过程(我们的浴室参与者)可以忽略它(即使有钥匙,有人仍然可以打开门) .

    二进制/互斥量和计数信号量之间也存在差异 .

    查看http://www.cs.columbia.edu/~jae/4118/lect/L05-ipc.html的讲义 .

  • 0

    我已经创建了可视化,这有助于理解这个想法 . 信号量控制多线程环境中对公共资源的访问 .
    enter image description here

    ExecutorService executor = Executors.newFixedThreadPool(6);
    
    Semaphore semaphore = new Semaphore(4);
    
    Runnable longRunningTask = () -> {
        boolean permit = false;
        try {
            permit = semaphore.tryAcquire(1, TimeUnit.SECONDS);
            if (permit) {
                System.out.println("Semaphore acquired");
                Thread.sleep(5);
            } else {
                System.out.println("Could not acquire semaphore");
            }
        } catch (InterruptedException e) {
            throw new IllegalStateException(e);
        } finally {
            if (permit) {
                semaphore.release();
            }
        }
    };
    
    // execute tasks
    for (int j = 0; j < 10; j++) {
        executor.submit(longRunningTask);
    }
    executor.shutdown();
    

    产量

    Semaphore acquired
    Semaphore acquired
    Semaphore acquired
    Semaphore acquired
    Could not acquire semaphore
    Could not acquire semaphore
    

    示例代码来自article

  • 9

    这是一个老问题,但信号量最有趣的用途之一是读/写锁,并没有明确提到它 .

    r / w锁以简单的方式工作:为读者消耗一个许可,为编写者提供所有许可 . 实际上,一个简单的r / w锁定实现,但需要读取元数据(实际上两次),这可能成为一个瓶颈,仍然明显优于互斥锁或锁 .

    另一个缺点是编写器也可以很容易地启动,除非信号量是公平的,或者写入在多个请求中获得许可,在这种情况下,它们需要在它们之间有明确的互斥 .

    进一步read

  • 1

    信号量是一种锁定资源以保证资源的方法在执行一段代码时,只有这段代码可以访问该资源 . 这使得两个线程不会同时访问资源,这可能会导致问题 .

相关问题