如何防止两个操作相互交错,同时仍然允许并发执行?

Posted

技术标签:

【中文标题】如何防止两个操作相互交错,同时仍然允许并发执行?【英文标题】:How can I prevent two operations from interleaving with each other whilst still allowing concurrent execution? 【发布时间】:2014-05-27 15:42:46 【问题描述】:

我有两种方法,foo()bar()。将有多个线程调用这些方法,可能同时调用。如果foo()bar() 同时运行可能会很麻烦,因为它们内部逻辑的交错可能会使系统处于不一致的状态。但是,多个线程能够同时调用foo() 并且多个线程能够同时调用bar() 是完全可以的,而且实际上是可取的。最后一个条件是foo() 应该尽快返回,而bar() 没有硬时间限制。

我一直在考虑最好的控制这种行为的各种方法。以最简单的形式使用synchronized 是行不通的,因为这会阻止对每个方法的并发调用。起初我认为ReadWriteLock 可能很合适,但这只会允许其中一个方法与其自身同时调用。我考虑的另一种可能性是将这些方法的请求排队在两个单独的队列中,并有一个消费者将同时执行队列中的每个foo(),然后执行队列中的每个bar(),但这似乎很难调整以避免不必要的阻塞foo()

有什么建议吗?

【问题讨论】:

是否有机会使包含foobar 方法的对象不可变? 可能没有帮助 - 在这种情况下,内部逻辑涉及远程 API 调用和数据库访问。 【参考方案1】:

我认为一个好的解决方案是创建一个单独的类来控制对每个方法的访问。您将创建此类的单例,然后使用它来控制何时可以继续输入任一方法。

这是第三次迭代。这个可以防止饥饿。

可以在 foo() 调用之外使用:

em.enterFoo(Thread.currentThread());
foo();
em.exitFoo();

但如果可能的话,可能会像在 foo() 的入口和出口处调用那样更干净。

代码:

public static class EntryManager

    private int inFoo = 0;
    private int inBar = 0;
    private Queue<Thread> queue = new LinkedList<>();

    public synchronized void enterBar(Thread t) throws InterruptedException
    
        // Place the Thread on the queue
        queue.add(t);

        while(queue.peek() != t)
        
            // Wait until the passed Thread is at the head of the queue.
            this.wait();
        

        while(inFoo > 0)
        
            // Wait until there is no one in foo().
            this.wait();
        
        // There is no one in foo.  So this thread can enter bar.
        // Remove the thread from the queue.
        queue.remove();         
        inBar++;    
        // Wakeup everyone.
        this.notifyAll();
    

    public synchronized void enterFoo(Thread t) throws InterruptedException
    
        // Place the thread on the queue
        queue.add(t);

        while(queue.peek() != t)
        
            // Wait until the passed Thread is at the head of the queue.
            this.wait();
        

        while(inBar > 0)
        
            this.wait();
        
        // There is no one in bar.  So this thread can enter foo.
        // Remove the thread from the queue.
        queue.remove(); 
        inFoo++;
        // Wakeup everyone.
        this.notifyAll();
    

    public synchronized void exitBar()
    
        inBar--;
        // Wakeup everyone.
        this.notifyAll();
    

    public synchronized void exitFoo()
    
        inFoo--;
        // Wakeup everyone.
        this.notifyAll();
    

【讨论】:

清晰简洁。但我认为应该添加等待 foo 或 bar 可用的操作。 这是一个公平的概念,但正如所写的那样,它不会防止饥饿 - 一个操作的稳定流会阻止尝试执行另一个操作的线程完成。【参考方案2】:

我不知道这个问题的名称,所以我会编写自己的同步助手对象来处理它。听起来很多类似于读/写锁,除了读/写锁允许同时有任意数量的读者,或者只允许一个写者,但不能同时允许两者;您的锁将允许任意数量的 foo() 或任意数量的 bar(),但不能同时允许。

棘手的部分是确保锁是公平的。如果没有争用没问题,但是如果锁处于“foo”模式,并且有源源不断的线程想要调用 foo(),而只有一两个想要调用 bar() 的线程怎么办。 bar() 线程是如何运行的?

实际上,它让我想起了很多繁忙的高速公路交叉口的红绿灯。红绿灯可以让汽车在东/西路线上行驶,或在北/南路线上行驶,但不能同时在两者上行驶。您不希望灯切换太频繁,并且每个周期只让一两辆车通过,因为那样效率低下。但您也不希望灯光让司机等太久而生气。

我有一种感觉,该政策可能必须针对您的特定应用进行定制。即,这可能取决于调用这两个函数的频率、是否被突发调用等。

我会从读写器锁的源代码开始,并尝试破解它,直到它对我有用为止。

【讨论】:

这就是我可能最终会做的事情(重写 ReadWriteLock),但我想看看是否有人确实首先认识到它是一个已解决的问题。

以上是关于如何防止两个操作相互交错,同时仍然允许并发执行?的主要内容,如果未能解决你的问题,请参考以下文章

这个 JWT 实现是不是可以防止 XSS 和 CSRF 攻击,同时仍然允许我访问有效负载?

操作系统:进程间的相互作用(多线程基础)

Oracle新手入门:如何提高索引创建速度?

防止 C++ 中的交错

如何防止碰撞中的两个物体同时交叉

当多个警报视图出现在同一个视图控制器中时,有没有办法交错?