什么是信号量?

发布于 2024-07-05 19:59:34 字数 66 浏览 6 评论 0原文

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

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

A semaphore is a programming concept that is frequently used to solve multi-threading problems. My question to the community:

What is a semaphore and how do you use it?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(15

吻风 2024-07-12 19:59:35

互斥量:对资源的独占成员访问

信号量:对资源的 n 成员访问

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

信号量可以做同样的事情,但支持固定的访问量。同时呼叫者的数量。 例如,我可以将数据库调用包装在信号量 (3) 中,以便我的多线程应用程序最多可以有 3 个同时连接来访问数据库。 所有尝试都将被阻止,直到三个插槽之一打开为止。 它们使诸如天真的节流之类的事情变得非常非常容易。

Mutex: exclusive-member access to a resource

Semaphore: n-member access to a resource

That is, a mutex can be used to syncronize access to a counter, file, database, etc.

A sempahore can do the same thing but supports a fixed number of simultaneous callers. For example, I can wrap my database calls in a semaphore(3) so that my multithreaded app will hit the database with at most 3 simultaneous connections. All attempts will block until one of the three slots opens up. They make things like doing naive throttling really, really easy.

最丧也最甜 2024-07-12 19:59:35

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

因此,互斥体允许对资源(如操作系统线程)进行独占访问,而信号量 是一次允许访问n 个资源。

Consider, a taxi that can accommodate a total of 3(rear)+2(front) persons including the driver. So, a semaphore allows only 5 persons inside a car at a time.
And a mutex allows only 1 person on a single seat of the car.

Therefore, Mutex is to allow exclusive access for a resource (like an OS thread) while a Semaphore is to allow access for n number of resources at a time.

栀子花开つ 2024-07-12 19:59:35

@克雷格:

信号量是一种锁定信号量的方法
资源,从而保证
当一段代码被执行时,
只有这段代码可以访问
该资源。 这保留了两个线程
从同时访问资源,
这可能会导致问题。

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

@Craig:

A semaphore is a way to lock a
resource so that it is guaranteed that
while a piece of code is executed,
only this piece of code has access to
that resource. This keeps two threads
from concurrently accesing a resource,
which can cause problems.

This is not restricted to only one thread. A semaphore can be configured to allow a fixed number of threads to access a resource.

揽月 2024-07-12 19:59:35

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

信号量是一种编程结构,可以通过实现同步和互斥来帮助我们实现并发。 信号量有两种类型:二进制信号量和计数信号量。

信号量有两部分:计数器和等待访问特定资源的任务列表。 信号量执行两种操作:等待 (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);
}

上例中,只有B1执行完毕后,B2才能执行。 假设线程 A 首先执行 - 到达 sem.P(),然后等待,因为计数器为 0(已关闭)。 线程 B 出现,完成 B1,然后释放线程 A - 然后线程 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 个任务 - #1 和 2,您希望在 3 之后执行。您将如何设计同步?

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,可以基于某些特定的优先级等。 (注意:如果您希望 t1 和 t2 以某种特定顺序执行,并且您不知道信号量的实现,请考虑如何安排 P 和 V;s)

(了解:如果V 的数量大于 P 的数量?)

使用计数信号量进行互斥:我希望您为此构建自己的伪代码(让您更好地理解事物!) - 但基本概念是这样的:计数信号量counter = N允许N个任务自由进入临界区。 这意味着你有 N 个任务(或者线程,如果你喜欢的话)进入临界区,但是第 N+1 个任务被阻塞(在我们最喜欢的阻塞任务列表中),并且只有当某人 V 是信号量时才允许通过至少一次。 因此,信号量计数器不再在 0 和 1 之间波动,而是在 0 和 N 之间波动,允许 N 个任务自由进入和退出,不会阻塞任何人!

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

思考:仅通过计数信号量就可以实现互斥吗? 如果您有 10 个资源实例,并且有 10 个线程进入(通过计数信号量)并尝试使用第一个实例,该怎么办?

There are two essential concepts to building concurrent programs - synchronization and mutual exclusion. We will see how these two types of locks (semaphores are more generally a kind of locking mechanism) help us achieve synchronization and mutual exclusion.

A semaphore is a programming construct that helps us achieve concurrency, by implementing both synchronization and mutual exclusion. Semaphores are of two types, Binary and Counting.

A semaphore has two parts : a counter, and a list of tasks waiting to access a particular resource. A semaphore performs two operations : wait (P) [this is like acquiring a lock], and release (V)[ similar to releasing a lock] - these are the only two operations that one can perform on a semaphore. In a binary semaphore, the counter logically goes between 0 and 1. You can think of it as being similar to a lock with two values : open/closed. A counting semaphore has multiple values for count.

What is important to understand is that the semaphore counter keeps track of the number of tasks that do not have to block, i.e., they can make progress. Tasks block, and add themselves to the semaphore's list only when the counter is zero. Therefore, a task gets added to the list in the P() routine if it cannot progress, and "freed" using the V() routine.

Now, it is fairly obvious to see how binary semaphores can be used to solve synchronization and mutual exclusion - they are essentially locks.

ex. Synchronization:

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);
}

In the above example, B2 can only execute after B1 has finished execution. Let's say thread A comes executes first - gets to sem.P(), and waits, since the counter is 0 (closed). Thread B comes along, finishes B1, and then frees thread A - which then completes B2. So we achieve synchronization.

Now let's look at mutual exclusion with a binary semaphore:

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);
}

The mutual exclusion is quite simple as well - m1 and m2 cannot enter the critical section at the same time. So each thread is using the same semaphore to provide mutual exclusion for its two critical sections. Now, is it possible to have greater concurrency? Depends on the critical sections. (Think about how else one could use semaphores to achieve mutual exclusion.. hint hint : do i necessarily only need to use one semaphore?)

Counting semaphore: A semaphore with more than one value. Let's look at what this is implying - a lock with more than one value?? So open, closed, and ...hmm. Of what use is a multi-stage-lock in mutual exclusion or synchronization?

Let's take the easier of the two:

Synchronization using a counting semaphore: Let's say you have 3 tasks - #1 and 2 you want executed after 3. How would you design your synchronization?

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();
}

So if your semaphore starts off closed, you ensure that t1 and t2 block, get added to the semaphore's list. Then along comes all important t3, finishes its business and frees t1 and t2. What order are they freed in? Depends on the implementation of the semaphore's list. Could be FIFO, could be based some particular priority,etc. (Note : think about how you would arrange your P's and V;s if you wanted t1 and t2 to be executed in some particular order, and if you weren't aware of the implementation of the semaphore)

(Find out : What happens if the number of V's is greater than the number of P's?)

Mutual Exclusion Using counting semaphores: I'd like you to construct your own pseudocode for this (makes you understand things better!) - but the fundamental concept is this : a counting semaphore of counter = N allows N tasks to enter the critical section freely. What this means is you have N tasks (or threads, if you like) enter the critical section, but the N+1th task gets blocked (goes on our favorite blocked-task list), and only is let through when somebody V's the semaphore at least once. So the semaphore counter, instead of swinging between 0 and 1, now goes between 0 and N, allowing N tasks to freely enter and exit, blocking nobody!

Now gosh, why would you need such a stupid thing? Isn't the whole point of mutual exclusion to not let more than one guy access a resource?? (Hint Hint...You don't always only have one drive in your computer, do you...?)

To think about : Is mutual exclusion achieved by having a counting semaphore alone? What if you have 10 instances of a resource, and 10 threads come in (through the counting semaphore) and try to use the first instance?

青瓷清茶倾城歌 2024-07-12 19:59:35

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

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

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

Semaphore can also be used as a ... semaphore.
For example if you have multiple process enqueuing data to a queue, and only one task consuming data from the queue. If you don't want your consuming task to constantly poll the queue for available data, you can use semaphore.

Here the semaphore is not used as an exclusion mechanism, but as a signaling mechanism.
The consuming task is waiting on the semaphore
The producing task are posting on the semaphore.

This way the consuming task is running when and only when there is data to be dequeued

妄断弥空 2024-07-12 19:59:35

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

示例:如果您有一个包含 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();
            }
        } 
    }

Semaphores are act like thread limiters.

Example: If you have a pool of 100 threads and you want to perform some DB operation. If 100 threads access the DB at a given time, then there may be locking issue in DB so we can use semaphore which allow only limited thread at a time.Below Example allow only one thread at a time. When a thread call the acquire() method, it will then get the access and after calling the release() method, it will release the acccess so that next thread will get the access.

    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();
            }
        } 
    }
↙温凉少女 2024-07-12 19:59:35

我创建了可视化,这应该有助于理解这个想法。 信号量控制对多线程环境中公共资源的访问。
输入图像描述这里

ExecutorService executor = Executors.newFixedThreadPool(7);

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
Could not acquire semaphore

来自 文章

I've created the visualization which should help to understand the idea. Semaphore controls access to a common resource in a multithreading environment.
enter image description here

ExecutorService executor = Executors.newFixedThreadPool(7);

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();

Output

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

Sample code from the article

泅人 2024-07-12 19:59:35

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

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

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

A semaphore is an object containing a natural number (i.e. a integer greater or equal to zero) on which two modifying operations are defined. One operation, V, adds 1 to the natural. The other operation, P, decreases the natural number by 1. Both activities are atomic (i.e. no other operation can be executed at the same time as a V or a P).

Because the natural number 0 cannot be decreased, calling P on a semaphore containing a 0 will block the execution of the calling process(/thread) until some moment at which the number is no longer 0 and P can be successfully (and atomically) executed.

As mentioned in other answers, semaphores can be used to restrict access to a certain resource to a maximum (but variable) number of processes.

追星践月 2024-07-12 19:59:35

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

A hardware or software flag. In multi tasking systems , a semaphore is as variable with a value that indicates the status of a common resource.A process needing the resource checks the semaphore to determine the resources status and then decides how to proceed.

将军与妓 2024-07-12 19:59:35

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

现在想象两个进程试图同时去洗手间。 这不是一个好的情况,信号量用于防止这种情况发生。 不幸的是,信号量是一种自愿机制,进程(我们上厕所的人)可以忽略它(即即使有钥匙,有人仍然可以直接把门踢开)。

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

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

So imagine everyone is trying to go to the bathroom and there's only a certain number of keys to the bathroom. Now if there's not enough keys left, that person needs to wait. So think of semaphore as representing those set of keys available for bathrooms (the system resources) that different processes (bathroom goers) can request access to.

Now imagine two processes trying to go to the bathroom at the same time. That's not a good situation and semaphores are used to prevent this. Unfortunately, the semaphore is a voluntary mechanism and processes (our bathroom goers) can ignore it (i.e. even if there are keys, someone can still just kick the door open).

There are also differences between binary/mutex & counting semaphores.

Check out the lecture notes at http://www.cs.columbia.edu/~jae/4118/lect/L05-ipc.html.

姜生凉生 2024-07-12 19:59:35

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

r/w 锁的工作方式很简单:为读取者消耗一个许可,为写入者消耗所有许可。
事实上,ar/w 锁的一个简单实现,但需要在读取时进行元数据修改(实际上两次),这可能成为瓶颈,但仍然比互斥锁或锁好得多。

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

进一步阅读

This is an old question but one of the most interesting uses of semaphore is a read/write lock and it has not been explicitly mentioned.

The r/w locks works in simple fashion: consume one permit for a reader and all permits for writers.
Indeed, a trivial implementation of a r/w lock but requires metadata modification on read (actually twice) that can become a bottle neck, still significantly better than a mutex or lock.

Another downside is that writers can be started rather easily as well unless the semaphore is a fair one or the writes acquire permits in multiple requests, in such case they need an explicit mutex between themselves.

Further read:

少跟Wǒ拽 2024-07-12 19:59:35

互斥量只是一个布尔值,而信号量是一个计数器

两者都用于锁定部分代码,这样它就不会被太多线程访问。

示例

lock.set()
a += 1
lock.unset()

现在,如果lock 是一个互斥体,则意味着无论有多少线程尝试访问受保护的代码片段,它都将始终被锁定或解锁(表面下的布尔值)。 锁定时,任何其他线程都会等待,直到它被前一个线程解锁/取消设置。

现在想象一下,如果 lock 是在引擎盖下一个具有预定义 MAX 值的计数器(在我们的示例中为 2)。 然后,如果 2 个线程尝试访问该资源,则锁会将其值增加到 2。如果第 3 个线程随后尝试访问该资源,它只会等待计数器低于 2,依此类推。

如果锁作为信号量的最大值为 1,那么它将完全充当互斥锁。

Mutex is just a boolean while semaphore is a counter.

Both are used to lock part of code so it's not accessed by too many threads.

Example

lock.set()
a += 1
lock.unset()

Now if lock was a mutex, it means that it will always be locked or unlocked (a boolean under the surface) regardless how many threads try access the protected snippet of code. While locked, any other thread would just wait until it's unlocked/unset by the previous thread.

Now imagine if instead lock was under the hood a counter with a predefined MAX value (say 2 for our example). Then if 2 threads try to access the resource, then lock would get its value increased to 2. If a 3rd thread then tried to access it, it would simply wait for the counter to go below 2 and so on.

If lock as a semaphore had a max of 1, then it would be acting exactly as a mutex.

天冷不及心凉 2024-07-12 19:59:35

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

A semaphore is a way to lock a resource so that it is guaranteed that while a piece of code is executed, only this piece of code has access to that resource. This keeps two threads from concurrently accesing a resource, which can cause problems.

若水微香 2024-07-12 19:59:34

将信号量想象为夜总会的保镖。 有一定数量的人可以同时进入俱乐部。 如果俱乐部已满,则任何人都不允许进入,但一旦一个人离开,另一个人就可能进入。

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

这是一个非常有教学意义的 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);
        }
    }
}

Think of semaphores as bouncers at a nightclub. There are a dedicated number of people that are allowed in the club at once. If the club is full no one is allowed to enter, but as soon as one person leaves another person might enter.

It's simply a way to limit the number of consumers for a specific resource. For example, to limit the number of simultaneous calls to a database in an application.

Here is a very pedagogic example in 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);
        }
    }
}
拥醉 2024-07-12 19:59:34

Michael Barr 的文章 互斥体和信号量揭秘 是一篇很棒的文章简短介绍互斥体和信号量的不同之处,以及何时应该使用和不应该使用它们。 我在这里摘录了几个关键段落。

关键点是互斥锁应该用于保护共享资源,而信号量应该用于发信号。 通常不应使用信号量来保护共享资源,也不应使用互斥体来发送信号。 例如,在使用信号量保护共享资源方面,保镖类比存在问题 - 您可以以这种方式使用它们,但可能会导致难以诊断错误。

虽然互斥体和信号量在实现上有一些相似之处,但它们的使用方式应该有所不同。

对于顶部提出的问题,最常见(但仍然不正确)的答案是互斥体和信号量非常相似,唯一显着的区别是信号量的计数可以大于 1。 几乎所有工程师似乎都正确理解互斥锁是一个二进制标志,用于通过确保代码关键部分内的互斥来保护共享资源。 但是,当被要求扩展如何使用“计数信号量”时,大多数工程师(仅在信心程度上有所不同)表达了一些教科书观点,即这些信号量用于保护多个等效资源。

...

在这一点上,使用浴室钥匙的想法进行了一个有趣的类比来保护共享资源 - 浴室。 如果商店只有一间浴室,那么一把钥匙就足以保护该资源并防止多人同时使用它。

如果有多个浴室,人们可能会想为它们设置相同的密钥并制作多个密钥 - 这类似于信号量被误用。 一旦你有了钥匙,你实际上并不知道哪个浴室是可用的,如果你沿着这条路走下去,你可能最终会使用互斥体来提供该信息,并确保你不会占用已经被占用的浴室。

信号量是保护多个本质上相同的资源的错误工具,但这就是许多人的想法和使用方式。 保镖的类比明显不同 - 不存在多个相同类型的资源,而是有一个资源可以同时接受多个用户。 我认为信号量可以在这种情况下使用,但在现实世界中很少有这种类比真正成立的情况 - 更常见的是存在多个相同类型但仍然是单独的资源,例如浴室,无法使用这边走。

...

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

...

这里强调一点,互斥体以不良方式干扰实时操作系统,导致优先级倒置,由于资源共享,不太重要的任务可能会在更重要的任务之前执行。 简而言之,当优先级较低的任务使用互斥体获取资源 A,然后尝试获取 B,但由于 B 不可用而暂停时,就会发生这种情况。 当它等待时,一个更高优先级的任务出现并需要 A,但它已经被一个甚至没有运行的进程占用,因为它正在等待 B。有很多方法可以解决这个问题,但大多数情况下它是固定的通过更改互斥体和任务管理器。 在这些情况下,互斥体比二进制信号量复杂得多,并且在这种情况下使用信号量将导致优先级反转,因为任务管理器不知道优先级反转并且无法采取行动来纠正它。

...

现代社会对互斥体和信号量的广泛混淆是有历史原因的,因为它可以追溯到 1974 年 Edsger Dijkstra 发明的信号量(本文中大写“S”)。 在此之前,计算机科学家已知的中断安全任务同步和信号机制都无法有效地扩展以供两个以上的任务使用。 Dijkstra 革命性的、安全的、可扩展的信号量被应用于关键部分保护和信号发送。 混乱就这样开始了。

然而,随着基于优先级的抢占式 RTOS(例如,VRTX,约 1980 年)的出现、建立 RMA 的学术论文的发表以及优先级反转引起的问题以及1990 年关于优先级继承协议的论文 3 很明显,互斥体必须不仅仅是带有二进制计数器的信号量。

互斥体:资源共享

信号量:信号发送

在没有仔细考虑副作用的情况下,不要将其中一个用于另一个。

The article Mutexes and Semaphores Demystified by Michael Barr is a great short introduction into what makes mutexes and semaphores different, and when they should and should not be used. I've excerpted several key paragraphs here.

The key point is that mutexes should be used to protect shared resources, while semaphores should be used for signaling. You should generally not use semaphores to protect shared resources, nor mutexes for signaling. There are issues, for instance, with the bouncer analogy in terms of using semaphores to protect shared resources - you can use them that way, but it may cause hard to diagnose bugs.

While mutexes and semaphores have some similarities in their implementation, they should always be used differently.

The most common (but nonetheless incorrect) answer to the question posed at the top is that mutexes and semaphores are very similar, with the only significant difference being that semaphores can count higher than one. Nearly all engineers seem to properly understand that a mutex is a binary flag used to protect a shared resource by ensuring mutual exclusion inside critical sections of code. But when asked to expand on how to use a "counting semaphore," most engineers—varying only in their degree of confidence—express some flavor of the textbook opinion that these are used to protect several equivalent resources.

...

At this point an interesting analogy is made using the idea of bathroom keys as protecting shared resources - the bathroom. If a shop has a single bathroom, then a single key will be sufficient to protect that resource and prevent multiple people from using it simultaneously.

If there are multiple bathrooms, one might be tempted to key them alike and make multiple keys - this is similar to a semaphore being mis-used. Once you have a key you don't actually know which bathroom is available, and if you go down this path you're probably going to end up using mutexes to provide that information and make sure you don't take a bathroom that's already occupied.

A semaphore is the wrong tool to protect several of the essentially same resource, but this is how many people think of it and use it. The bouncer analogy is distinctly different - there aren't several of the same type of resource, instead there is one resource which can accept multiple simultaneous users. I suppose a semaphore can be used in such situations, but rarely are there real-world situations where the analogy actually holds - it's more often that there are several of the same type, but still individual resources, like the bathrooms, which cannot be used this way.

...

The correct use of a semaphore is for signaling from one task to another. A mutex is meant to be taken and released, always in that order, by each task that uses the shared resource it protects. By contrast, tasks that use semaphores either signal or wait—not both. For example, Task 1 may contain code to post (i.e., signal or increment) a particular semaphore when the "power" button is pressed and Task 2, which wakes the display, pends on that same semaphore. In this scenario, one task is the producer of the event signal; the other the consumer.

...

Here an important point is made that mutexes interfere with real time operating systems in a bad way, causing priority inversion where a less important task may be executed before a more important task because of resource sharing. In short, this happens when a lower priority task uses a mutex to grab a resource, A, then tries to grab B, but is paused because B is unavailable. While it's waiting, a higher priority task comes along and needs A, but it's already tied up, and by a process that isn't even running because it's waiting for B. There are many ways to resolve this, but it most often is fixed by altering the mutex and task manager. The mutex is much more complex in these cases than a binary semaphore, and using a semaphore in such an instance will cause priority inversions because the task manager is unaware of the priority inversion and cannot act to correct it.

...

The cause of the widespread modern confusion between mutexes and semaphores is historical, as it dates all the way back to the 1974 invention of the Semaphore (capital "S", in this article) by Edsger Dijkstra. Prior to that date, none of the interrupt-safe task synchronization and signaling mechanisms known to computer scientists was efficiently scalable for use by more than two tasks. Dijkstra's revolutionary, safe-and-scalable Semaphore was applied in both critical section protection and signaling. And thus the confusion began.

However, it later became obvious to operating system developers, after the appearance of the priority-based preemptive RTOS (e.g., VRTX, ca. 1980), publication of academic papers establishing RMA and the problems caused by priority inversion, and a paper on priority inheritance protocols in 1990, 3 it became apparent that mutexes must be more than just semaphores with a binary counter.

Mutex: resource sharing

Semaphore: signaling

Don't use one for the other without careful consideration of the side effects.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文