信号量死锁



所以我在使用信号量时遇到了问题。编写一个代码,其中有4个房间和一些访客。每个房间都有一定的访客数量上限。因此,进入一个完整的房间会触发等待()。访客在进入另一个房间之前不得离开另一个房间,因此他们始终在一个房间内。

public class Semaphore {
  private int placesLeft;
  public Semaphore(int placesInRoom) {
    this.placesLeft = placesInRoom;
  }
  public synchronized void acquire(Visitor visitor) {
    Semaphore sem = visitor.getRoom().getSemaphore();
    try {
      while (placesLeft <= 0) {
        this.wait();
    }
  } catch (InterruptedException e) {}
  sem.release();
  placesLeft--;
}
public synchronized void release() {
  placesLeft++;
  this.notifyAll();
}

当 2 个人试图进入对方的房间时出现死锁。同样由于某种原因,placesLeft计数不正确。

那我该怎么办?

编辑:

一直忙着别的事情,重新回答这个问题。由于房间已满,问题不会出现,当房间 1 中的人员 1 想要进入房间 2 并且房间 2 中的人员 2 想要进入房间 1 时,会发生锁定。正如我所理解的那样,它可能与同步有关?它们在发布之前会卡住,因此不调用释放。据我了解,一个房间的安装和释放不能同时调用。所以基本上 room1 信号量释放不能在调用 accuire 的同时调用 cuz,房间 2 也是如此?我是新手编码员,同步还不清楚。从一个或另一个中删除同步似乎不起作用(也非常错误)。

与其实现你自己的,不如使用Java标准库中内置的java.util.concurrent.Semaphore怎么样?

java.util.concurrent包有一个很棒的教程,涵盖了信号量和它提供的许多其他有用的同步机制。

当依赖关系图中存在循环时,会发生死锁。当两个人试图进入对方的房间时,这显然是一个循环,僵局是自然的结果。

但是,您希望以其他方式处理周期:当周期发生时,人们都沿着周期移动(可以有超过 2 人交换房间)。

因此,您应该首先确定是否形成了一个循环,然后更改访问者的位置。

添加要Room的当前访客列表,以便您可以acquire签入,以了解传入访客来自此房间的某个居住者正在等待进入的房间的情况。您还需要添加访客等待进入的房间以Visitor

Room comingFrom = visitor.getRoom();
while (placesLeft <= 0) {
    for (Visitor waiter : room.getVisitors()) {
        if (waiter.getWaitingForRoom().equals(comingFrom) {
            // swap the visitors without releasing/acquiring any semaphores and return
        }
    }
    this.wait();
}

我有点不确定检查访客是否正在等待进入当前访客离开的同一房间的逻辑。鉴于代码,我无法分辨room代表哪个房间。

要回答您的问题,"我该怎么办?",如果您检测到死锁,请将其中一个死锁访客移动到任何有空间的房间。 然后把他搬到他真正想要的房间。 这基本上允许在不违反以下任何规则的情况下进行交换。

  1. 房间内访客不得超过 X 人
  2. 访客总是在一个房间
  3. 一次只能有一个访客更换房间(这确实是问题的症结)

请记住,那里有无数个信号量锁定策略......

试试这个:

public class Semaphore {
  private final static Object LOCK = new Object();
  private int placesLeft;
  public Semaphore(int placesInRoom) {
    this.placesLeft = placesInRoom;
  }
  public void acquire(Visitor visitor) {
      synchronized (LOCK) {
          Semaphore sem = visitor.getRoom().getSemaphore();
          try {
              while (placesLeft <= 0) {
                  LOCK.wait();
              }
          } catch (InterruptedException e) {}
        sem.release();
        placesLeft--;
    }
}
public void release() {
    synchronized(LOCK) {
        placesLeft++;
        LOCK.notifyAll();
    }
}

各个信号量实例上同步的旧代码。防止死锁非常困难,因为一个实例的acquire()方法调用另一个实例的release()。然后,如果另一个线程当前正在该其他实例上执行 acquire() 方法,则对 release() 的调用将阻止。如果第二个线程最终在第一个实例上调用release(),则会出现死锁。

我将各个信号量实例上的同步替换为名为 LOCK 的单个对象的同步。执行acquire()的线程已锁定LOCK的监视器。因此,此线程在调用 release() 方法时不会被阻塞。因此,release()方法将始终终止。这解决了僵局。

死锁通常由分层信号量系统解决。典型的死锁看起来像

流程 A

getSemaphore('A');
getSemaphore('B');

流程 B

getSemaphore('B');
getSemaphore('A');

只是为了让所有进程在 B 之前选择 A。这可以通过编写 getSemaphore 函数来强制实施带有断言的层次结构来实现。

对于您的具体情况,这并不能非常明显地解决问题,但您可以从这个想法中推断。

创建迁移队列。当用户想要更改房间时,您的函数可能如下所示:

ChangeRoom(person, from, to)
{
    getSemaphore('room_queue', 3200);
    enqueue(room_queue, Object(person, from, to));
    releaseSemaphore('room_queue');
}

"3200"是信号量超时。如果一个进程在请求信号量后被中断,它仍然会使系统死锁。这将提供 1 小时的超时。您可以根据系统稳定性将其设置为 1 分 5 秒的逻辑值。然后有一个队列处理器,它一次只允许使用非阻塞信号量进行一次传输

QueueProcessor()
{
    getSemaphore('room_queue', 3200);
    for (transition = dequeue(room_queue))
    {
       if (getNonBlockingSemaphore(transition.to)
       {
          releaseSemaphore(transition.from);
          getSemaphore(transition.to);
       }
       continue;
    }
    releaseSemaphore('room_queue');
    sleep(10);
}

睡眠使队列进程不堪重负。将其设置为适当的检查。您还可以设置中断,以仅在房间打开空间或添加了过渡时拉取队列项目。这样,如果一个房间已经满了,它就不会浪费时间试图进入,但每个人都至少有一次机会立即进入。

这会强制转换以获取队列信号量,然后才能获取房间信号量。设置无死锁层次结构。如果房间已满,用户将永远不会离开队列,但它不会使系统死锁。

最新更新