É verdade que o C ++ 0x virá sem semáforos? Já existem algumas perguntas no Stack Overflow sobre o uso de semáforos. Eu os uso (semáforos posix) o tempo todo para deixar um thread aguardar algum evento em outro thread:
void thread0(...)
{
doSomething0();
event1.wait();
...
}
void thread1(...)
{
doSomething1();
event1.post();
...
}
Se eu faria isso com um mutex:
void thread0(...)
{
doSomething0();
event1.lock(); event1.unlock();
...
}
void thread1(...)
{
event1.lock();
doSomethingth1();
event1.unlock();
...
}
Problema: É feio e não é garantido que o thread1 bloqueie o mutex primeiro (como o mesmo thread deve bloquear e desbloquear um mutex, você também não pode bloquear o evento1 antes do início do thread0 e do thread1).
Portanto, como o boost também não possui semáforos, qual é a maneira mais simples de conseguir o que foi mencionado acima?
Respostas:
Você pode criar facilmente um a partir de um mutex e uma variável de condição:
fonte
while(!count_)
loop.Com base na resposta de Maxim Yegorushkin , tentei fazer o exemplo no estilo C ++ 11.
fonte
cv.wait(lck, [this]() { return count > 0; });
Decidi escrever o semáforo C ++ 11 mais robusto / genérico que eu pudesse, no estilo do padrão, tanto quanto eu poderia (observe
using semaphore = ...
, você normalmente usaria o nomesemaphore
semelhante ao normalmente usandostring
nãobasic_string
):fonte
wait_for
and dowait_until
método com o predicado retornam um valor booleano (não um `std :: cv_status).std::size_t
está sem sinal, portanto, decrementá-lo abaixo de zero é UB, e sempre será>= 0
. IMHOcount
deve ser umint
.de acordo com os semáforos posix, eu acrescentaria
E eu prefiro usar um mecanismo de sincronização em um nível conveniente de abstração, em vez de sempre copiar colando uma versão costurada usando operadores mais básicos.
fonte
Você também pode conferir o cpp11-on-multicore - ele possui uma implementação de semáforo portátil e ideal.
O repositório também contém outros itens de threading que complementam o threading do c ++ 11.
fonte
Você pode trabalhar com variáveis mutex e condição. Você obtém acesso exclusivo com o mutex, verifique se deseja continuar ou precisa esperar pelo outro lado. Se precisar esperar, aguarde em uma condição. Quando o outro encadeamento determina que você pode continuar, ele sinaliza a condição.
Há um pequeno exemplo na biblioteca boost :: thread que você provavelmente pode copiar (as bibliotecas C ++ 0x e boost thread são muito semelhantes).
fonte
wait()
será traduzido para "travar, verifique a contagem se houver um decréscimo diferente de zero e continue; se a espera zero na condição" enquantopost
seria "travar, contador de incremento, sinaliza se foi 0 "Também pode ser útil o invólucro de semáforo RAII nos threads:
Exemplo de uso no aplicativo multithread:
fonte
O C ++ 20 finalmente terá semáforos -
std::counting_semaphore<max_count>
.Estes terão (pelo menos) os seguintes métodos:
acquire()
(bloqueando)try_acquire()
(sem bloqueio, retorna imediatamente)try_acquire_for()
(sem bloqueio, dura uma duração)try_acquire_until()
(sem bloqueio, leva um tempo para parar de tentar)release()
Isso ainda não está listado na cppreference, mas você pode ler os slides da apresentação do CppCon 2019 ou assistir ao vídeo . Há também a proposta oficial P0514R4 , mas não tenho certeza de que seja a versão mais atualizada.
fonte
Eu encontrei o shared_ptr e o fraco_ptr, um longo com uma lista, fez o trabalho que eu precisava. Meu problema era que eu tinha vários clientes que desejavam interagir com os dados internos de um host. Normalmente, o host atualiza os dados por conta própria; no entanto, se um cliente solicita, o host precisa parar de atualizar até que nenhum cliente esteja acessando os dados do host. Ao mesmo tempo, um cliente pode solicitar acesso exclusivo, para que nenhum outro cliente, nem o host, possa modificar os dados do host.
Como fiz isso, criei uma struct:
Cada cliente teria um membro desses:
Em seguida, o host teria um membro fraco_ptr para exclusividade e uma lista de fraco_ptrs para bloqueios não exclusivos:
Há uma função para ativar o bloqueio e outra função para verificar se o host está bloqueado:
Testo bloqueios em LockUpdate, IsUpdateLocked e periodicamente na rotina de atualização do host. Testar um bloqueio é tão simples quanto verificar se o fraco_ptr expirou e remover qualquer um que expirou da lista m_locks (eu só faço isso durante a atualização do host); posso verificar se a lista está vazia; ao mesmo tempo, recebo o desbloqueio automático quando um cliente redefine o shared_ptr no qual está pendurado, o que também acontece quando um cliente é destruído automaticamente.
O efeito geral é que, como os clientes raramente precisam de exclusividade (normalmente reservados apenas para adições e exclusões), na maioria das vezes uma solicitação para LockUpdate (false), ou seja, não exclusiva, é bem-sucedida desde que (! M_exclusiveLock). E um LockUpdate (true), um pedido de exclusividade, é bem-sucedido apenas quando ambos (! M_exclusiveLock) e (m_locks.empty ()).
Uma fila pode ser adicionada para atenuar entre bloqueios exclusivos e não exclusivos; no entanto, até o momento não tive colisões, então pretendo esperar até que isso aconteça para adicionar a solução (principalmente para que eu tenha uma condição de teste no mundo real).
Até agora, isso está funcionando bem para minhas necessidades; Eu posso imaginar a necessidade de expandir isso, e alguns problemas que podem surgir sobre o uso expandido, no entanto, isso foi rápido de implementar e exigiu muito pouco código personalizado.
fonte
Caso alguém esteja interessado na versão atômica, aqui está a implementação. O desempenho é esperado melhor que a versão da variável mutex & condition.
fonte
wait
código tenha que repetir várias vezes. Quando finalmente for desbloqueado, será necessário a mãe de todos os ramos imprevisíveis, pois a previsão de loop da CPU certamente preverá que ele fará um loop novamente. Eu poderia listar muitos outros problemas com esse código.wait
loop consumirá recursos de microexecução da CPU à medida que gira. Suponha que ele esteja no mesmo núcleo físico do encadeamento que deverianotify
- ele diminuirá muito o encadeamento.wait
loop para o mesmo semáforo. Ambos estão gravando a toda velocidade na mesma linha de cache, o que pode reduzir a velocidade de outros núcleos saturando barramentos entre núcleos.