简体   繁体   English

为什么`ExecutorService`不能一致地调度线程?

[英]Why cannot `ExecutorService` consistently schedule threads?

I am attempting to reimplement my concurrent code using CyclicBarrier which is new to me.我正在尝试使用CyclicBarrier重新实现我的并发代码,这对我来说是新的。 I can do without it but am time trialling it against my other solution, the problem I have is a deadlock situation with the following code:我可以不用它,但是我正在针对我的其他解决方案进行时间试验,我遇到的问题是以下代码的死锁情况:

//instance variables (fully initialised elsewhere).
private final ExecutorService exec = Executors.newFixedThreadPool(4);
private ArrayList<IListener> listeners = new ArrayList<IListener>();
private int[] playerIds;

private class WorldUpdater {
    final CyclicBarrier barrier1;
    final CyclicBarrier barrier2;
    volatile boolean anyChange;
    List<Callable<Void>> calls = new ArrayList<Callable<Void>>();

    class SyncedCallable implements Callable<Void> {
        final IListener listener;

        private SyncedCallable(IListener listener) {
            this.listener = listener;
        }

        @Override
        public Void call() throws Exception {
            listener.startUpdate();
            if (barrier1.await() == 0) {
                anyChange = processCommons();
            }
            barrier2.await();
            listener.endUpdate(anyChange);
            return null;
        }
    }

    public WorldUpdater(ArrayList<IListener> listeners, int[] playerIds) {
        barrier2 = new CyclicBarrier(listeners.size());
        barrier1 = new CyclicBarrier(listeners.size());
        for (int i : playerIds)
            calls.add(new SyncedCallable(listeners.get(i)));
    }

    void start(){
        try {
            exec.invokeAll(calls);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


void someMethodCalledEveryFrame() {
    //Calls some Fisher-something method that shuffles int[]
    shufflePIDs();  
    WorldUpdater updater = new WorldUpdater(listeners, playerIds);
    updater.start();
}

I use the debugger in Android Studio (intelliJ) to pause execution at this stage.我使用 Android Studio (intelliJ) 中的调试器在此阶段暂停执行。 I get multiple threads showing the my await calls as the last of my code to be executed我得到多个线程显示我的await调用作为我要执行的最后一个代码

-> Unsafe.park -> Unsafe.park

-> LockSupport.park -> LockSupport.park

-> AbstractQueuedSynchronizer$ConditionObject.await -> AbstractQueuedSynchronizer$ConditionObject.await

-> CyclicBarrier.doWait -> CyclicBarrier.doWait

-> CyclicBarrier.await -> CyclicBarrier.await

At least one thread will be have this stack:至少一个线程将拥有此堆栈:

-> Unsafe.park . -> Unsafe.park

-> LockSupport.park -> LockSupport.park

-> AbstractQueuedSynchronizer$ConditionObject.await -> AbstractQueuedSynchronizer$ConditionObject.await

-> LinkedBlockingQueue.take -> LinkedBlockingQueue.take

-> ThreadPoolExecutor.getTask -> ThreadPoolExecutor.getTask

-> ThreadPoolExecutor.runWorker -> ThreadPoolExecutor.runWorker

-> ThreadPoolExecutor$Worker.run -> ThreadPoolExecutor$Worker.run

-> Thread.run -> Thread.run

I notice that the CyclicBarrier plays no part in these latter stray threads.我注意到CyclicBarrier在后面的这些杂散线程中不起作用。

processCommons is calling exec.invokeAll (on the 3 listeners), I suppose this means I am running out of threads. processCommons正在调用exec.invokeAll (在 3 个侦听器上),我想这意味着我的线程用完了。 But many times this doesn't happen so please could someone clarify why ExecutorService cannot consistently schedule my threads?但是很多时候这不会发生,所以请有人澄清为什么ExecutorService不能始终如一地安排我的线程? They have their own stack and program counter so I would have thought this to not be a problem.他们有自己的堆栈和程序计数器,所以我认为这不是问题。 I only ever have max 4 running at once.我一次最多只能运行 4 个。 Someone help me with the math?有人帮我做数学吗?

What is the value of listeners.size() when your WorldUpdater is created?创建WorldUpdaterlisteners.size()的值是多少? If it is more than four, then your threads will never get past the barrier.如果超过四个,那么您的线程将永远不会越过障碍。

Your ExecutorService has exactly four threads.您的ExecutorService正好有四个线程。 No more, no fewer.不多也不少。 The callers of barrier1.await() and barrier2.await() will not get past the barrier until exactly listeners.size() threads are waiting.listeners.size()线程正在等待之前, barrier1.await()barrier2.await()的调用者不会越过屏障。

My gut reaction is, it would be a mistake for pool threads to use a CyclicBarrier .我的直觉是,池线程使用CyclicBarrier是错误的。 CyclicBarrier is only useful when you know exactly how many threads will be using it. CyclicBarrier只有在您确切知道有多少线程将使用它时才有用。 But, when you're using a thread pool, you often do not know the size of the pool.但是,当您使用线程池时,您通常知道池的大小。 In fact, in a real-world (ie, commercial) application, if you're using a thread pool, It probably was not created by your code at all.事实上,在现实世界(即商业)应用程序中,如果您使用的是线程池,它可能根本不是由您的代码创建的。 It probably was created somewhere else, and passed in to your code as an injected dependency .它可能是在其他地方创建的,并作为注入的依赖项传递到您的代码中。

I did a little experiment and came up with:我做了一个小实验,得出了:

        @Override
        public Void call() throws Exception {
            System.out.println("startUpdate, Thread:" + Thread.currentThread());
            listener.startUpdate();
            if (barrier1.await() == 0) {
                System.out.println("processCommons, Thread:" + Thread.currentThread());
                anyChange = processCommons();
            }
            barrier2.await();
            System.out.println("endUpdate, Thread:" + Thread.currentThread());
            listener.endUpdate(anyChange);
            return null;
        }

Which revealed when using a pool of 3 with 3 listeners , I will always hang in processCommons which contains the following:当使用具有 3 个listeners的 3 个池时,我将始终挂在processCommons ,其中包含以下内容:

    List<Callable<Void>> calls = new ArrayList<Callable<Void>>();
    for (IListener listiner : listeners)
        calls.add(new CommonsCallable(listener));
    try {
        exec.invokeAll(calls);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

With 2 threads waiting at the barrier and the third attempting to create 3 more.有 2 个线程在屏障处等待,第三个线程试图再创建 3 个。 I needed one extra thread in the ExecutorService and the 2 at the barrier could be "recycled" as I was asking in my question.我在ExecutorService需要一个额外的线程,而屏障处的 2 个线程可以在我提出问题时“回收”。 I've got references to 6 threads at this stage when exec is only holding 4. This can run happily for many minutes.在这个阶段,当exec只持有 4 个线程时,我得到了 6 个线程的引用。这可以愉快地运行几分钟。

private final ExecutorService exec = Executors.newFixedThreadPool(8);

Should be better, but it was not.应该更好,但事实并非如此。

Finally I did breakpoint stepping in intelliJ (thanks ideaC!)最后我在 intelliJ 中做了断点(感谢ideaC!)

The problem is问题是

        if (barrier1.await() == 0) {
            anyChange = processCommons();
        }
        barrier2.await();

Between the 2 await you may get several suspended threads that haven't actually reached the await .在 2 await之间,您可能会得到几个实际上尚未到达await挂起线程。 In the case of 3 listeners out of a pool of 4 it only takes one to get "unscheduled" (or whatever) and barrier2 will never get the full complement.在 4 个听众中有 3 个听众的情况下,只需要一个人就可以“不定期”(或其他),而barrier2永远不会得到完整的补充。 But what about when I have a pool of 8?但是当我有 8 个池时呢? The same behaviour manifests with all but two of the threads the stack of limbo:除了两个线程之外的所有线程都表现出相同的行为:

->Unsafe.park. ->Unsafe.park。

->LockSupport.park ->LockSupport.park

->AbstractQueuedSynchronizer$ConditionObject.await ->AbstractQueuedSynchronizer$ConditionObject.await

->LinkedBlockingQueue.take ->LinkedBlockingQueue.take

->ThreadPoolExecutor.getTask ->ThreadPoolExecutor.getTask

->ThreadPoolExecutor.runWorker ->ThreadPoolExecutor.runWorker

->ThreadPoolExecutor$Worker.run ->ThreadPoolExecutor$Worker.run

->Thread.run ->Thread.run

What can be happening here to disable all 5 threads?这里会发生什么来禁用所有 5 个线程? I should have taken James Large's advice and avoided crowbarring in this over elaborate CyclicBarrier .--UPDATE-- It can run all night now without CyclicBarrier .我应该接受 James Large 的建议,避免在这个过于复杂的CyclicBarrier撬棍。--更新--它现在可以在没有CyclicBarrier情况下整夜运行。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM