信号量是一种经常用于解决多线程问题的编程概念。我向社区提出的问题:
什么是信号量?你如何使用它?
答案 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.两个活动都是原子的(即,其他操作不能与V
或P
同时执行)。 / p>
因为自然数0不能减少,所以在包含0的信号量上调用P
将阻止调用进程(/ thread)的执行,直到该数字不再为0和{{ 1}}可以成功(和原子)执行。
如其他答案中所述,信号量可用于将对某个资源的访问限制为最大(但可变)的进程数。
答案 8 :(得分:2)
我已经创建了可视化效果,应该可以帮助您理解这个想法。信号量控制在多线程环境中对公共资源的访问。
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”。一个非负且在线程之间共享的变量是信号量。
此变量用于解决临界区问题。以及在多处理环境中实现进程同步。
信号量有两种类型:
二进制信号量:这也称为互斥锁。它只能有 0 和 1 两个值。初始值为 1。用于解决多进程临界区问题。
计数信号量:它的值可用于控制对任何域的访问。可用于限制对多个实例的访问资源。
答案 14 :(得分:-2)
信号量是一种锁定资源的方法,以确保在执行一段代码时,只有这段代码才能访问该资源。这使得两个线程不会同时访问资源,这可能会导致问题。