什么是信号量?

时间:2008-08-29 15:58:16

标签: multithreading concurrency semaphore

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

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

15 个答案:

答案 0 :(得分:371)

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

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

这是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 :(得分:170)

答案 2 :(得分:68)

Mutex:对资源的独占成员访问

信号量:n成员访问资源

也就是说,可以使用互斥锁同步访问计数器,文件,数据库等。

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

答案 3 :(得分:18)

@Craig:

  

信号量是一种锁定方式   资源,以确保它   在执行一段代码时   只有这段代码才能访问   那资源。这保留了两个线程   从同时访问资源,   这可能会导致问题。

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

答案 4 :(得分:15)

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

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

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

答案 5 :(得分:10)

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

信号量是一种编程结构,它通过实现同步和互斥来帮助我们实现并发。信号量有两种类型,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 + 1个任务被阻止(进入我们最喜欢的被阻止任务列表),只有当有人V是信号量时才通过至少一次。所以信号量计数器,而不是在0和1之间摆动,现在介于0和N之间,允许N个任务自由进入和退出,阻止任何人!

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

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

答案 6 :(得分:10)

考虑一下,可以容纳总共3个()+ 2个()人的出租车,包括司机。因此, semaphore 一次只允许5个人在车内。 一个 mutex 只允许一个人坐在汽车的一个座位上。

因此,在 Mutex 允许一次访问 n 数量的资源。

答案 7 :(得分:6)

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

因为自然数0不能减少,所以在包含0的信号量上调用P将阻止调用进程(/ thread)的执行,直到该数字不再为0和{{ 1}}可以成功(和原子)执行。

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

答案 8 :(得分:2)

我已经创建了可视化效果,应该可以帮助您理解这个想法。信号量控制在多线程环境中对公共资源的访问。 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 :(得分:1)

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

答案 10 :(得分:1)

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

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

二进制/互斥和&amp;之间也存在差异。计算信号量。

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

答案 11 :(得分:1)

信号量的作用类似于线程限制器。

示例:如果您有100个线程池,并且想要执行一些数据库操作。如果在给定的时间有100个线程访问数据库,那么数据库中可能存在锁定问题,因此我们可以使用信号量,该信号量一次仅允许有限的线程。在下面的示例中,一次仅允许一个线程。当线程调用acquire()方法时,它将获得访问权限,而在调用release()方法后,它将释放访问权限,以便下一个线程将获得访问权限。

    package practice;
    import java.util.concurrent.Semaphore;

    public class SemaphoreExample {
        public static void main(String[] args) {
            Semaphore s = new Semaphore(1);
            semaphoreTask s1 = new semaphoreTask(s);
            semaphoreTask s2 = new semaphoreTask(s);
            semaphoreTask s3 = new semaphoreTask(s);
            semaphoreTask s4 = new semaphoreTask(s);
            semaphoreTask s5 = new semaphoreTask(s);
            s1.start();
            s2.start();
            s3.start();
            s4.start();
            s5.start();
        }
    }

    class semaphoreTask extends Thread {
        Semaphore s;
        public semaphoreTask(Semaphore s) {
            this.s = s;
        }
        @Override
        public void run() {
            try {
                s.acquire();
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName()+" Going to perform some operation");
                s.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } 
    }

答案 12 :(得分:0)

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

r / w锁以简单的方式工作:为读者使用一个许可证,为作者使用所有许可证。 实际上,一个简单的r / w锁定实现,但需要对读取进行元数据修改(实际上是两次)才能成为瓶颈,仍然明显优于互斥锁或锁。

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

进一步read

答案 13 :(得分:0)

Dijkstra 在 1965 年提出了信号量。它是使用整数值管理并发进程的重要技术。它被称为“Semaphore”。一个非负且在线程之间共享的变量是信号量。

此变量用于解决临界区问题。以及在多处理环境中实现进程同步。

信号量有两种类型:

  1. 二进制信号量
  2. 计数信号量

二进制信号量:这也称为互斥锁。它只能有 0 和 1 两个值。初始值为 1。用于解决多进程临界区问题。

计数信号量:它的值可用于控制对任何域的访问。可用于限制对多个实例的访问资源。

答案 14 :(得分:-2)

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