Acordos espúrios em Java realmente acontecem?


208

Vendo várias perguntas relacionadas ao bloqueio e (quase) sempre encontrando o 'loop por causa de termos de ativação espúrios' 1 Gostaria de saber se alguém experimentou esse tipo de ativação (assumindo um ambiente decente de hardware / software, por exemplo)?

Sei que o termo "espúrio" significa que não há razão aparente, mas quais podem ser as razões para esse tipo de evento?

( 1 Nota: não estou questionando a prática de loop.)

Edit: Uma pergunta auxiliar (para quem gosta de amostras de código):

Se eu tenho o seguinte programa e o executo:

public class Spurious {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition cond = lock.newCondition();
        lock.lock();
        try {
            try {
                cond.await();
                System.out.println("Spurious wakeup!");
            } catch (InterruptedException ex) {
                System.out.println("Just a regular interrupt.");
            }
        } finally {
            lock.unlock();
        }
    }
}

O que posso fazer para acordar awaitespúria sem esperar eternamente por um evento aleatório?


1
Para as JVMs executadas em sistemas POSIX e que usam pthread_cond_wait()a pergunta real é "Por que pthread_cond_wait tem ativações espúrias?" .
Flow

Respostas:


204

O artigo da Wikipedia sobre despertares espúrios tem este boato:

A pthread_cond_wait()função no Linux é implementada usando a futexchamada do sistema. Cada chamada do sistema de bloqueio no Linux retorna abruptamente EINTRquando o processo recebe um sinal. ... pthread_cond_wait()não é possível reiniciar a espera porque pode perder uma ativação real no pouco tempo em que esteve fora da futexchamada do sistema. Essa condição de corrida só pode ser evitada pelo chamador que procura invariante. Um sinal POSIX, portanto, gera uma ativação espúria.

Resumo : Se um processo Linux for sinalizado, seus threads em espera terão uma ativação agradável e quente e espúria .

Eu comprei isso. É uma pílula mais fácil de engolir do que a razão normalmente vaga de "é para desempenho".


13
Melhor explicação aqui: stackoverflow.com/questions/1461913/...
Gili

3
Esse desbloqueio do EINTR é válido para todas as chamadas do sistema de bloqueio em sistemas derivados do Unix. Isso tornou o kernel muito mais simples, mas os programadores de aplicativos compraram o fardo.
Tim Williscroft

2
Eu pensei que pthread_cond_wait () e seus amigos não pudessem retornar o EINTR, mas retornar zero se acordados espúrios? De: pubs.opengroup.org/onlinepubs/7908799/xsh/… "Essas funções não retornarão um código de erro de [EINTR]."
Gubby

2
@jgubby Isso mesmo. A futex()chamada subjacente retorna EINTR, mas esse valor de retorno não é alterado para o próximo nível. O chamador pthread deve, portanto, verificar se há invariável. O que eles estão dizendo é que, quando pthread_cond_wait()retorna, você deve verificar sua condição de loop (invariável) novamente, porque a espera pode ter sido espúriamente acordada. Receber um sinal durante uma chamada do sistema é uma causa possível, mas não é a única.
John Kugelman

1
Presumivelmente, a pthreadbiblioteca poderia fornecer sua própria lógica invariável e sua própria verificação, a fim de eliminar despertares espúrios, em vez de passar essa responsabilidade para o usuário. Presumivelmente, isso teria o impacto reivindicado no desempenho.

22

Eu tenho um sistema de produção que exibe esse comportamento. Um encadeamento aguarda um sinal de que há uma mensagem na fila. Nos períodos de maior movimento, até 20% das ativações são falsas (ou seja, quando acorda, não há nada na fila). Esse encadeamento é o único consumidor das mensagens. É executado em uma caixa com 8 processadores Linux SLES-10 e é construído com o GCC 4.1.2. As mensagens vêm de uma fonte externa e são processadas de forma assíncrona porque há problemas se o meu sistema não as lê com rapidez suficiente.


15

Para responder à pergunta do título - Sim! Embora o artigo do Wiki mencione muito sobre despertos espúrios, uma boa explicação para o mesmo que me deparei é a seguinte:

Basta pensar nisso ... como qualquer código, o agendador de threads pode sofrer um apagão temporário devido a algo anormal acontecendo no hardware / software subjacente. Obviamente, deve-se tomar cuidado para que isso aconteça o mais raro possível, mas como não existe um software 100% robusto, é razoável supor que isso possa acontecer e tomar cuidado com a recuperação normal, caso o agendador detecte isso (por exemplo, observando batimentos cardíacos ausentes).

Agora, como o agendador poderia se recuperar, levando em consideração que durante o blecaute poderia perder alguns sinais destinados a notificar threads em espera? Se o planejador não fizer nada, os encadeamentos "azarados" mencionados simplesmente travarão, aguardando para sempre - para evitar isso, o planejador simplesmente enviaria um sinal para todos os encadeamentos em espera.

Isso torna necessário estabelecer um "contrato" para que o thread em espera possa ser notificado sem um motivo. Para ser mais preciso, haveria um motivo - blecaute do planejador - mas como o encadeamento foi projetado (por um bom motivo) para não dar atenção aos detalhes de implementação interna do planejador, é provável que seja melhor apresentar isso como "espúrio".

Eu estava lendo esta resposta da Source e achei razoável o suficiente. Leia também

Acordos espúrios em Java e como evitá-los .

PS: O link acima é para o meu blog pessoal, com detalhes adicionais sobre despertares espúrios.


9

Um tempo atrás, Cameron Purdy escreveu um post no blog sobre ser atingido pelo espúrio problema de ativação. Então sim, acontece

Suponho que esteja na especificação (como uma possibilidade) devido às limitações de algumas das plataformas nas quais o Java é implantado? embora eu possa estar errado!


Li o post e me deu uma idéia de ter testes de unidade para testar a conformidade de uma aplicação ao paradigma loop-wait, ativando-a aleatoriamente / deterministicamente. Ou já está disponível em algum lugar?
akarnokd 26/06/09

É outra pergunta sobre o SO: "Existe uma VM rigorosa que pode ser usada para teste?". Eu adoraria ver um com memória estrita segmento local - Eu não acho que eles existem ainda
oxbow_lakes

8

Apenas para adicionar isso. Sim, isso aconteceu e passei três dias pesquisando a causa de um problema de multiencadeamento em uma máquina de 24 núcleos (JDK 6). 4 de 10 execuções experimentaram isso sem nenhum padrão. Isso nunca aconteceu em 2 ou 8 núcleos.

Estudou algum material on-line e esse não é um problema de Java, mas um comportamento geral raro, mas esperado.


Olá ReneS, você está desenvolvendo o aplicativo em execução no site? ( Possui ) o método wait () chamando enquanto o loop verifica a condição externa, conforme sugerido em java doc docs.oracle.com/javase/6/docs/api/java/lang/… ?
gumkins

Eu escrevi sobre isso e sim, a solução é um loop while com uma verificação de condição. Meu erro foi o loop que faltava ... mas aprendi sobre essas ativações ... nunca em dois núcleos, geralmente em 24cores blog.xceptance.com/2011/05/06/spurious-wakeup-the-rare-event
ReneS

Tive experiências semelhantes quando executei um aplicativo em um servidor unix de mais de 40 núcleos. Teve uma quantidade extrema de despertares espúrios. - Portanto, parece que a quantidade de ativações espúrias é diretamente proporcional à quantidade de núcleos de processador do sistema.
bvdb

0

https://stackoverflow.com/a/1461956/14731 contém uma excelente explicação de por que você precisa se proteger contra ativações espúrias, mesmo se o sistema operacional subjacente não as acionar. É interessante notar que esta explicação se aplica a várias linguagens de programação, incluindo Java.


0

Respondendo à pergunta do OP

O que posso fazer para despertar essa espuria sem esperar eternamente por um evento aleatório?

, nenhuma ativação espúria poderia ativar esse segmento em espera!

Independentemente de as ativações espúrias poderem ou não acontecer em uma plataforma específica, em um caso do snippet do OP, é positivamente impossível paraCondition.await() retornar e ver a linha "despertar espúrias!" no fluxo de saída.

A menos que você esteja usando uma Java Class Library muito exótica

Isso ocorre porque standard, OpenJDK 's ReentrantLockmétodo' s newCondition()retorna a AbstractQueuedSynchronizer'implementação s de Conditioninterface, aninhada ConditionObject(a propósito, é a única implementação da Conditioninterface, neste biblioteca de classes), eo ConditionObject' s método await()em si verifica se a condição não espera e nenhuma ativação espúria pode forçar esse método a retornar por engano.

A propósito, você pode verificar por si próprio, pois é muito fácil imitar a ativação espúria, uma vez que a AbstractQueuedSynchronizerimplementação baseada em está envolvida. AbstractQueuedSynchronizerusa baixo nível LockSupport's parke unparkmétodos, e se você invocar LockSupport.unparkem um thread aguardando emCondition , esta ação não pode ser distinguido de um despertar espúria.

Refatorando levemente o snippet do OP,

public class Spurious {

    private static class AwaitingThread extends Thread {

        @Override
        public void run() {
            Lock lock = new ReentrantLock();
            Condition cond = lock.newCondition();
            lock.lock();
            try {
                try {
                    cond.await();
                    System.out.println("Spurious wakeup!");
                } catch (InterruptedException ex) {
                    System.out.println("Just a regular interrupt.");
                }
            } finally {
                lock.unlock();
            }
        }
    }

    private static final int AMOUNT_OF_SPURIOUS_WAKEUPS = 10;

    public static void main(String[] args) throws InterruptedException {
        Thread awaitingThread = new AwaitingThread();
        awaitingThread.start();
        Thread.sleep(10000);
        for(int i =0 ; i < AMOUNT_OF_SPURIOUS_WAKEUPS; i++)
            LockSupport.unpark(awaitingThread);
        Thread.sleep(10000);
        if (awaitingThread.isAlive())
            System.out.println("Even after " + AMOUNT_OF_SPURIOUS_WAKEUPS + " \"spurious wakeups\" the Condition is stil awaiting");
        else
            System.out.println("You are using very unusual implementation of java.util.concurrent.locks.Condition");
    }
}

, e não importa o quanto o encadeamento (principal) não-estacionário tentaria ativar o encadeamento em espera, o Condition.await()método nunca retornará nesse caso.

Os despertares espúrios Conditiondos métodos em espera são discutidos no javadoc da Conditioninterface . Embora diga isso,

ao esperar por uma Condição, é permitido que ocorra uma ativação espúria

e essa

é recomendável que os programadores de aplicativos sempre assumam que eles podem ocorrer e, portanto, sempre esperem em um loop.

mas depois acrescenta que

Uma implementação é gratuita para remover a possibilidade de despertares espúrios

e AbstractQueuedSynchronizera implementação da Conditioninterface faz exatamente isso - remove qualquer possibilidade de despertares espúrios .

Isso certamente vale para os outros ConditionObjectmétodos que aguardam.

Então, a conclusão é:

devemos sempre chamar Condition.awaitno loop e verificar se a condição não se mantém, mas com o OpenJDK padrão, o Java Class Library nunca pode acontecer . A menos que, novamente, você use Java Class Library muito incomum (o que deve ser muito incomum, porque outras bibliotecas de classes Java não OpenJDK conhecidas, atualmente quase extintas GNU Classpath e Apache Harmony , parecem ter idênticas à implementação padrão da Conditioninterface)

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.