Por que o pthread_cond_wait tem ativações espúrias?

Para citar a página man:

Ao usar variables ​​de condição, sempre há um predicado booleano envolvendo variables ​​compartilhadas associadas a cada condição de espera que é verdadeira se o encadeamento deve continuar. Os despertares espúrios das funções pthread_cond_timedwait () ou pthread_cond_wait () podem ocorrer. Como o retorno de pthread_cond_timedwait () ou pthread_cond_wait () não implica em nada sobre o valor desse predicado, o predicado deve ser reavaliado após esse retorno.

Então, pthread_cond_wait pode retornar mesmo que você não tenha sinalizado. À primeira vista, pelo menos, isso parece muito atroz. Seria como uma function que retornou aleatoriamente o valor errado ou retornou aleatoriamente antes que ele realmente atingisse uma declaração de retorno adequada. Parece um grande erro. Mas o fato de que eles optaram por documentar isso na página do manual, em vez de consertá-lo, parece indicar que há uma razão legítima pela qual pthread_cond_wait acaba despertando espúriamente. Presumivelmente, há algo intrínseco sobre como funciona que faz com que isso não possa ser ajudado. A questão é o que.

Por que pthread_cond_wait retorna falsamente? Por que não pode garantir que só vai acordar quando for devidamente sinalizado? Alguém pode explicar a razão de seu comportamento espúrio?

A seguinte explicação é dada por David R. Butenhof em “Programação com Threads POSIX” (p. 80):

Despertares espúrios podem soar estranhos, mas em alguns sistemas multiprocessadores, tornando a ativação da condição completamente previsível pode diminuir substancialmente todas as operações de variables ​​de condição.

Na discussão de comp.programming.threads a seguir, ele expande o pensamento por trás do design:

 Patrick Doyle escreveu: 
 > No artigo, Tom Payne escreveu: 
 Kaz Kylheku escreveu: 
 >> É assim porque implementações às vezes não podem evitar a inserção 
 >> estes despertares espúrios;  pode ser caro impedi-los. 

 >> Mas por quê?  Por que isso é tão difícil?  Por exemplo, estamos falando de 
 >> situações em que uma espera expira apenas quando um sinal chega? 

 > Você sabe, eu me pergunto se os designers de pthreads usaram a lógica assim: 
 > os usuários das variables ​​de condição precisam verificar a condição ao sair, 
 > por isso não vamos colocar nenhum fardo adicional sobre eles se permitirmos 
 > despertares espúrios;  e como é concebível que permitir falsas 
 > os wakeups podem tornar uma implementação mais rápida, só pode ajudar se 
 > permita-os. 

 > Eles podem não ter tido nenhuma implementação específica em mente. 

 Você não está muito longe, a não ser que você não tenha avançado o suficiente. 

 A intenção era forçar o código correto / robusto exigindo loops de predicado.  Isso foi 
 impulsionado pelo provável contingente acadêmico entre os "principais tópicos" em 
 o grupo de trabalho, embora eu não ache que alguém realmente discordou da intenção 
 quando eles entenderam o que isso significava. 

 Nós seguimos essa intenção com vários níveis de justificação.  O primeiro foi que 
 "religiosamente" usando um loop protege o aplicativo contra o seu próprio imperfeito 
 práticas de codificação.  A segunda foi que não era difícil imaginar abstratamente 
 máquinas e código de implementação que poderia explorar este requisito para melhorar 
 o desempenho das operações de espera de condição média através da otimização do 
 mecanismos de synchronization. 
 / ------------------ [David.Buten ... @ compaq.com] ------------------ \ 
 |  Compaq Computer Corporation POSIX Thread Arquiteto | 
 |  Meu livro: http://www.awl.com/cseng/titles/0-201-63392-2/ | 
 \ ----- [http://home.earthlink.net/~anneart/family/dave.html] ----- / 

Há pelo menos duas coisas que o “despertar espúrio” pode significar:

  • Um encadeamento bloqueado em pthread_cond_wait pode retornar da chamada, mesmo que nenhuma chamada para sinalizar ou transmitir tenha ocorrido.
  • Um encadeamento bloqueado em pthread_cond_wait retorna devido a uma chamada para sinalizar ou transmitir, no entanto, depois de readquirir o mutex, o predicado subjacente não é mais verdadeiro.

Mas o último caso pode ocorrer mesmo se a implementação da variável de condição não permitir o primeiro caso. Considere uma fila de consumidores do produtor e três threads.

  • O thread 1 acabou de criar um elemento e liberou o mutex, e a fila está vazia. O thread está fazendo o que faz com o elemento que adquiriu em alguma CPU.
  • O thread 2 tenta desenfileirar um elemento, mas descobre que a fila está vazia quando marcada sob o mutex, chama pthread_cond_wait e bloqueia a chamada que está aguardando sinal / difusão.
  • O segmento 3 obtém o mutex, insere um novo elemento na fila, notifica a variável de condição e libera o bloqueio.
  • Em resposta à notificação do encadeamento 3, o encadeamento 2, que estava aguardando a condição, está agendado para ser executado.
  • No entanto, antes do thread 2 conseguir entrar na CPU e pegar o bloqueio da fila, o thread 1 conclui sua tarefa atual e retorna à fila para mais trabalho. Ele obtém o bloqueio de fila, verifica o predicado e descobre que há trabalho na fila. Ele prossegue para desenfileirar o item que o thread 3 inseriu, libera o bloqueio e faz o que faz com o item que o thread 3 enfileirou.
  • O segmento 2 agora entra em uma CPU e obtém o bloqueio, mas quando verifica o predicado, descobre que a fila está vazia. A linha 1 ‘roubou’ o item, então a ativação parece ser espúria. O segmento 2 precisa aguardar novamente a condição.

Portanto, como você sempre precisa verificar o predicado em um loop, não faz diferença se as variables ​​de condição subjacentes puderem ter outros tipos de ativações espúrias.

A seção “Múltiplos Despertadores por Sinal de Condição” em pthread_cond_signal tem uma implementação de exemplo de pthread_cond_wait e pthread_cond_signal que envolve wakekups espúrios.