【问题标题】:design of a Producer/Consumer app生产者/消费者应用程序的设计
【发布时间】:2009-03-21 13:22:55
【问题描述】:

我有一个生成索引的生产者应用程序(将其存储在一些内存树数据结构中)。消费者应用程序将使用索引来搜索部分匹配项。

我不希望消费者 UI 在生产者索引数据时必须阻止(例如通过一些进度条)。基本上,如果用户希望使用部分索引,它就会这样做。在这种情况下,生产者可能不得不停止索引一段时间,直到用户离开另一个屏幕。

大致上,我知道我需要等待/通知协议来实现这一点。我的问题:是否可以在生产者线程进行业务时使用等待/通知来中断生产者线程?我需要什么 java.util.concurrent 原语来实现这一点?

【问题讨论】:

    标签: java multithreading concurrency


    【解决方案1】:

    按照您所描述的方式,您没有理由需要等待/通知。只需同步访问您的数据结构,以确保在访问时处于一致状态。

    编辑:通过“同步访问”,我并不是指同步整个数据结构(最终会阻塞生产者或消费者)。相反,仅同步正在更新的那些位,并且仅在您更新它们时同步。您会发现生产者的大部分工作都可以以非同步方式进行:例如,如果您正在构建一棵树,您可以识别需要插入的节点,在该节点上同步,进行插入,然后继续。

    【讨论】:

    • 您提出的建议类似于数据库中的行锁定。目前树被锁定以允许一个线程通过。
    【解决方案2】:

    在您的生产者线程中,您可能有某种主循环。这可能是打断制片人的最佳场所。我建议你使用 java 5 中引入的 java 同步对象,而不是使用 wait() 和 notify()。

    你可能会做类似的事情

    class Indexer {
    
       Lock lock = new ReentrantLock();    
    
        public void index(){
            while(somecondition){
                this.lock.lock();
                try{
                    // perform one indexing step
                }finally{
                    lock.unlock();
                }
            }
        }
    
        public Item lookup(){
            this.lock.lock();
            try{
                // perform your lookup
            }finally{
                lock.unlock();
            }
        }
    }
    

    您需要确保每次索引器释放锁时,您的索引都处于一致、合法的状态。在这种情况下,当索引器释放锁时,它会为新的或等待的 lookup() 操作留下机会来获取锁、完成并释放锁,此时您的索引器可以继续执行下一步。如果当前没有 lookup() 正在等待,那么您的索引器只会重新获取锁本身并继续其下一个操作。

    如果您认为您可能有多个线程同时尝试进行查找,您可能需要查看 ReadWriteLock 接口和 ReentrantReadWriteLock 实现。

    当然,此解决方案是一种简单的方法。它将阻塞任何一个没有锁的线程。您可能想检查是否可以直接在数据结构上同步,但这可能会很棘手,因为构建索引往往会使用某种平衡树或 B-Tree 或其他节点插入远非微不足道的东西。

    我建议您先尝试这种简单的方法,然后看看它的行为方式是否适合您。如果没有,您可以尝试将索引步骤分解为更小的步骤,或者尝试仅在部分数据结构上进行同步。

    不要太担心锁定的性能,在java中非竞争锁定(当只有一个线程试图获取锁定时)很便宜。只要您的大部分锁定不满足,锁定性能就无需担心。

    【讨论】:

      【解决方案3】:

      生产者应用程序可以有两个索引:已发布和正在工作。生产者只能在工作中工作,消费者只能在已发布的情况下工作。一旦生产者完成索引,它可以用已发布的替换工作中的一个(通常交换一个指针)。如果会带来价值,生产者也可以发布部分索引的副本。这样可以避免长期锁定——当失去消费者访问索引时它会很有用。

      【讨论】:

        【解决方案4】:

        不,这是不可能的。

        在线程本身没有任何显式代码的情况下通知线程的唯一方法是使用 Thread.interrupt(),这将导致线程中的异常。不过,interrrupt() 通常不是很可靠,因为在代码中的某个随机点抛出异常是在所有代码路径中正确处理的噩梦。除此之外,线程某处(包括您使用的任何库)中的单个 try{}catch(Throwable){} 就足以吞噬信号。

        在大多数情况下,唯一正确的解决方案是使用共享标志或队列,消费者可以使用它们将消息传递给生产者。如果您担心生产者没有响应或冻结,请在单独的线程中运行它并要求它每 n 秒发送一次心跳消息。如果它没有发送心跳,就杀死它。 (请注意,确定生产者是否真的在冻结,而不仅仅是在等待外部事件,通常也很难做到正确)。

        【讨论】:

        • 这里有一些不错的想法,但也有一些彻底的错误。中断线程将不会“在某个随机点”引发异常。 InterruptedExceptions 被检查,并且可能抛出它们的方法是明确定义的。写得不好的“包罗万象”的块足以破坏任何程序。
        • 您对队列的建议很有趣。我能做的是让生产者每处理 10 个树节点就检查消费者的请求。我只是为了确保这看起来对用户有响应。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-03-14
        • 1970-01-01
        • 2012-10-13
        • 1970-01-01
        • 1970-01-01
        • 2013-11-10
        • 1970-01-01
        相关资源
        最近更新 更多