Java/GPars - мой пул потоков, кажется, "забит"

Что я делаю: я просматриваю таблицу компаний в базе данных... у каждой компании есть текст description поле, и внутри этого поля может быть несколько гиперссылок (редко больше 4). Я хочу протестировать эти ссылки, используя curlдля "плохого" ответа (обычно 404, но все, что не 200, будет представлять интерес).

Между прочим, это применимо к Java так же, как Groovy, без сомнения, и людям любого убеждения может быть интересно узнать, что базовый класс пула потоков, используемый здесь GPars (параллелизм Groovy) ForkJoinPool,

Собрав эти URL с помощью Matcher с использованием Pattern/(https?:.*?)\)/ Я получаю карту descripURLs "URL" -> "название компании". Тогда я использую withPool с большой пропускной способностью (из-за внутренней задержки ожидания ответов, очевидно), например, так:

startMillis = System.currentTimeMillis() 
AtomicInteger nRequest = new AtomicInteger()
AtomicInteger nResponsesReceived = new AtomicInteger()
poolObject = null
resultP = withPool( 50 ){ pool ->
    poolObject = pool
    descripURLs.eachParallel{ url, name ->
        int localNRequest = nRequest.incrementAndGet()
        Process process = checkURL( url )

        def response
        try {
            //// with the next line TIME PASSES in this Thread...
            response = process.text
        } catch( Exception e ) {
            System.err.println "$e"
        }
        // NB this line doesn't appear to make much difference
        process.destroyForcibly()
        nResponses = nResponsesReceived.incrementAndGet()
        int nRequestsNowMade = nRequest.get()
        if( response.trim() != '200' ) {
            println "\n*** request $localNRequest BAD RESPONSE\nname $name url $url\nresponse |$response|" +
                "\n$nRequestsNowMade made, outstanding ${nRequestsNowMade - nResponses}"
             // NB following line may of course not be printed immmediately after the above line, due to parallelism
            println "\nprocess poolSize $pool.poolSize, queuedTaskCount $pool.queuedTaskCount," +
                " queuedSubmissionCount? $pool.queuedSubmissionCount"   
        }
        println "time now ${System.currentTimeMillis() - startMillis}, activeThreadCount $pool.activeThreadCount"
    }
    println "END OF withPool iterations"
    println "pool $pool class ${pool.class.simpleName}, activeThreadCount $pool.activeThreadCount"
    pool.shutdownNow()
}

println "resultP $resultP class ${resultP.class.simpleName}"
println "pool $poolObject class ${poolObject.class.simpleName}"
println "pool shutdown? $poolObject.shutdown"

def checkURL( url ) {
    def process =  "curl -LI $url -o /dev/null -w '%{http_code}\n' -s".execute()
    // this appears necessary... otherwise potentially you can have processes hanging around forever
    process.waitForOrKill( 8000 ) // 8 s to get a reponse
    process.addShutdownHook{
        println "shutdown on url $url"
    }
    process
}

То, что я наблюдаю с пулом из 50 потоков, как указано выше, заключается в том, что для 500 URL потребуется 20 с. Я экспериментировал с меньшими и большими пулами, и 100, кажется, ничего не меняет, но 25 кажется медленнее, а 10 больше, чем 40 с. Времена также замечательно согласуются от запуска к запуску для того же размера пула.

Что я не понимаю, так это то, что ProcessКрюки отключения ES работают только в самом конце закрытия... для всех 500 Processэс! Это не означает, что на компьютере висит 500 реальных процессов: с помощью диспетчера задач я вижу, что число curl.exe Процессы в любой момент времени относительно невелики.

В то же время я наблюдаю от printlnЗдесь мы видим, что число активных потоков здесь начинается с 50, но затем уменьшается на протяжении всего цикла, достигая 3 (обычно) к концу. И еще... Я также могу заметить, что окончательные запросы добавляются только в самом конце цикла.

Это заставляет меня задуматься о том, не является ли пул потоков каким-то образом "забитым" этим "незаконченным бизнесом" этих "зомби" Processes... Я ожидаю, что окончательные запросы (из 500 сделанных) будут сделаны задолго до конца пробега. Есть ли способ, которым я могу закрыть эти Processраньше?

1 ответ

Решение

Ни Java, ни Groovy не поддерживают метод addShutdownHook на ребенка Process экземпляров.

Единственный метод addShutdownHook что Java поддерживает на Runtime пример. Это добавляет ловушку для запуска при выключении JVM.

Groovy добавляет удобство addShutdownHook() к Object класс, так что вам не нужно писать Runtime.getRuntime().addShutdownHook(..), но это ничего не меняет в базовом механизме: эти ловушки выполняются только при завершении работы JVM.

Потому что замыкания, которые вы добавляете с process.addShutdownHook Скорее всего, сохранить ссылки на process Например, они будут сохраняться до завершения работы JVM (объекты Java, но не процессы ОС)

Другие вопросы по тегам