监控goroutine和渠道泄漏/饥饿?

时间:2015-12-11 10:08:13

标签: clojure core.async

我有很多工人正在跑步,把工作项目排除在队列之外。类似的东西:

(def num-workers 100)

(def num-tied-up (atom 0))
(def num-started (atom 0))

(def input-queue (chan (dropping-buffer 200))

(dotimes [x num-workers]
  (go
    (swap num-started inc)
        (loop []
          (let [item (<! input-queue)]
            (swap! num-tied-up inc)
            (try 
              (process-f worker-id item)
              (catch Exception e _))
              (swap! num-tied-up dec))
          (recur)))
          (swap! num-started dec))))

希望num-tied-up代表在特定时间点执行工作的工人数量。 num-tied-up的值围绕相当一致的50,有时60。由于num-workers为100且num-started的值为100(即所有go例程正在运行),这感觉就像有一个舒适的边距。

我的问题是input-queue正在增长。我希望它能够徘徊在零标记附近,因为有足够的工人可以从中取出物品。但在实践中,最终它会最大化并丢弃事件。

tied-up看起来num-workers有足够的空间,所以工作人员可以从队列中取出工作。

我的问题:

  1. 我能做些什么来让它更健壮吗?
  2. 我可以使用任何其他诊断来解决出错的问题吗?有没有办法监控目前正在工作的goroutine数量?
  3. 您能否将观察结果与数据相符合?

2 个答案:

答案 0 :(得分:0)

当我修复代码的缩进时,这就是我所看到的:

(def num-workers 100)

(def num-tied-up (atom 0))
(def num-started (atom 0))

(def input-queue (chan (dropping-buffer 200))

(dotimes [x num-workers]
  (go
    (swap num-started inc)
    (loop []
      (let [item (<! input-queue)]
        (swap! num-tied-up inc)
        (try 
          (process-f worker-id item)
          (catch Exception e _))
          (swap! num-tied-up dec))
      (recur)))
  (swap! num-started dec))

)) ;; These parens don't balance.

忽略额外的parens,我假设是一些复制/粘贴错误,这里有一些观察:

  1. 您在go线程内增加num-started,但在创建该线程后立即将其减少到go线程之外。在递增之前,减量总是很有可能发生。
  2. 您创建的100个循环(每个线程一个)永远不会终止。这本身并不是一个问题,只要这是故意的和设计的。
  3. 请记住,产生一个go线程并不意味着当前线程(执行dotimes执行的产生的线程)将阻止。我可能会弄错,但看起来你的代码假设(swap! num-started dec)只会在紧接在它上面生成的go线程完成时运行。但事实并非如此,即使你的线程 最终完成了(如上所述,他们也没有)。

答案 1 :(得分:0)

go例程完成的工作不应该执行任何IO或阻塞操作(如线程/休眠),因为所有go例程共享相同的线程池,现在它具有固定大小cpus * 2 + 42。对于IO有界工作,请使用core.async/thread

请注意,线程池限制了将同时执行的go例程的数量,但是您可以有足够的等待执行。

作为类比,如果您启动Chrome,Vim和iTunes(相当于3个例行程序),但您的笔记本电脑中只有一个CPU(相当于1个大小为1的线程池),那么其中只有一个将执行在CPU中,另一个将等待执行。操作系统是负责暂停/恢复程序的操作系统,因此看起来它们都在同时运行。 Core.async也是这样做的,但不同的是,core.async可以在它们命中时暂停go-routines。

现在回答你的问题:

  1. 否。尝试/捕获所有异常是您的最佳选择。另外,监视队列的大小。我会重新评估core.async的需求。 Zach Tellman有一个非常好的thread pool implementation,有很多指标。
  2. 线程转储将显示所有core.async线程阻塞的位置,但正如我所说,你不应该在go-routine线程中做任何IO工作。看看core.async/pipeline-blocking
  3. 如果你有4个核心,你将得到一个50的core.async线程池,它与你对50个并发go块的观察结果相匹配。其他50个块正在运行,但要么等待工作出现在队列中,要么执行时隙。请注意,由于num-started为100,所以他们都有机会至少执行一次。
  4. 希望它有所帮助。