信号量是一种编程概念,经常用于解决多线程问题。我对社区的问题是:
什么是信号量,如何使用它?
信号量是一种编程概念,经常用于解决多线程问题。我对社区的问题是:
什么是信号量,如何使用它?
当前回答
这是一个老问题,但信号量最有趣的用途之一是读/写锁,但没有明确提到。
r/w锁的工作方式很简单:为读取器消耗一个许可证,为写入器消耗所有许可证。 事实上,一个简单的r/w锁实现,但需要在读取时修改元数据(实际上是两次),这可能会成为瓶颈,但仍然比互斥锁或锁好得多。
另一个缺点是,写入也可以很容易地启动,除非信号量是公平的,或者写入在多个请求中获得许可,在这种情况下,它们之间需要一个显式的互斥。
进一步阅读:
其他回答
我已经创建了可视化,这应该有助于理解这个想法。信号量控制多线程环境中对公共资源的访问。
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
本文中的示例代码
这是一个老问题,但信号量最有趣的用途之一是读/写锁,但没有明确提到。
r/w锁的工作方式很简单:为读取器消耗一个许可证,为写入器消耗所有许可证。 事实上,一个简单的r/w锁实现,但需要在读取时修改元数据(实际上是两次),这可能会成为瓶颈,但仍然比互斥锁或锁好得多。
另一个缺点是,写入也可以很容易地启动,除非信号量是公平的,或者写入在多个请求中获得许可,在这种情况下,它们之间需要一个显式的互斥。
进一步阅读:
信号量是一个包含自然数(即大于或等于零的整数)的对象,在自然数上定义了两个修改操作。一个运算V,给自然数加1。另一个操作,P,将自然数减少1。这两个活动都是原子的(即没有其他操作可以与V或P同时执行)。
因为自然数0不能减少,所以在包含0的信号量上调用P将阻塞调用进程(/thread)的执行,直到该数字不再为0,P可以成功(原子地)执行为止。
正如在其他回答中提到的,信号量可用于将对某个资源的访问限制为最大(但可变的)进程数。
信号量的作用类似于线程限制器。
示例:如果您有一个100个线程的池,并且您想执行一些DB操作。如果在给定的时间有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();
}
}
}
构建并发程序有两个基本概念——同步和互斥。我们将看到这两种类型的锁(信号量通常是一种锁定机制)如何帮助我们实现同步和互斥。
信号量是一种编程结构,通过实现同步和互斥来帮助我们实现并发。信号量有两种类型,二进制和计数。
信号量有两个部分:计数器和等待访问特定资源的任务列表。一个信号量执行两个操作:等待(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个任务——#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();
}
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)
(找出:如果V的数量大于P的数量会发生什么?)
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!
天啊,你为什么需要这么愚蠢的东西?互斥的全部意义不就是不让多于一个人访问资源吗?(提示提示…你的电脑里并不总是只有一个驱动器,对吧?)
思考一下:互斥是通过单独使用计数信号量实现的吗?如果您有一个资源的10个实例,并且有10个线程(通过计数信号量)进入并试图使用第一个实例,该怎么办?