Leitor / gravador bloqueia em C ++

Eu estou procurando um bom leitor / gravador de bloqueio em C ++. Temos um caso de uso de um único escritor pouco frequente e muitos leitores frequentes e gostaríamos de otimizar para isso. Preferível Eu gostaria de uma solução multi-plataforma, no entanto, um único Windows seria aceitável.

As versões mais recentes do boost :: thread possuem bloqueios de leitura / gravação (1.35.0 e posteriores, aparentemente as versões anteriores não funcionaram corretamente).

Eles têm os nomes shared_lock , unique_lock e upgrade_lock e operam em um shared_mutex .

Usar material pré-testado pré-testado padrão é sempre bom (por exemplo, Boost como outra resposta sugerida), mas isso é algo que não é muito difícil de se construir. Aqui está uma pequena implementação tirada de um projeto meu:

 #include  struct rwlock { pthread_mutex_t lock; pthread_cond_t read, write; unsigned readers, writers, read_waiters, write_waiters; }; void reader_lock(struct rwlock *self) { pthread_mutex_lock(&self->lock); if (self->writers || self->write_waiters) { self->read_waiters++; do pthread_cond_wait(&self->read, &self->lock); while (self->writers || self->write_waiters); self->read_waiters--; } self->readers++; pthread_mutex_unlock(&self->lock); } void reader_unlock(struct rwlock *self) { pthread_mutex_lock(&self->lock); self->readers--; if (self->write_waiters) pthread_cond_signal(&self->write); pthread_mutex_unlock(&self->lock); } void writer_lock(struct rwlock *self) { pthread_mutex_lock(&self->lock); if (self->readers || self->writers) { self->write_waiters++; do pthread_cond_wait(&self->write, &self->lock); while (self->readers || self->writers); self->write_waiters--; } self->writers = 1; pthread_mutex_unlock(&self->lock); } void writer_unlock(struct rwlock *self) { pthread_mutex_lock(&self->lock); self->writers = 0; if (self->write_waiters) pthread_cond_signal(&self->write); else if (self->read_waiters) pthread_cond_broadcast(&self->read); pthread_mutex_unlock(&self->lock); } void rwlock_init(struct rwlock *self) { self->readers = self->writers = self->read_waiters = self->write_waiters = 0; pthread_mutex_init(&self->lock, NULL); pthread_cond_init(&self->read, NULL); pthread_cond_init(&self->write, NULL); } 

pthreads não está realmente sendo nativo do Windows, mas a idéia geral está aqui. Esta implementação é ligeiramente tendenciosa para os escritores (uma horda de escritores pode passar fome aos leitores indefinidamente); apenas modifique writer_unlock se preferir que o equilíbrio seja o contrário.

Sim, isso é C e não C ++. A tradução é um exercício deixado ao leitor.

Editar

Greg Rogers apontou que o padrão POSIX especifica pthread_rwlock_* . Isso não ajuda se você não tiver pthreads , mas isso me fez lembrar: o Pthreads-w32 deve funcionar! Em vez de portar este código para não- pthreads para seu próprio uso, basta usar Pthreads-w32 no Windows e pthreads nativos em qualquer outro lugar.

Você pode usar o boost para criar um bloqueio de leitura / gravação:

 #include  #include  typedef boost::shared_mutex Lock; typedef boost::unique_lock< Lock > WriteLock; typedef boost::shared_lock< Lock > ReadLock; Lock myLock; void ReadFunction() { ReadLock r_lock(myLock); //Do reader stuff } void WriteFunction() { WriteLock w_lock(myLock); //Do writer stuff } 

Qualquer coisa que você decida usar, compare sua carga de trabalho com bloqueios simples, já que os bloqueios de leitura / gravação tendem a ser de 3-40x mais lentos que um simples mutex, quando não há contenção.

Aqui está alguma referência

Edit: O link da MSDN Magazine não está mais disponível. O artigo CodeProject está agora disponível em https://www.codeproject.com/Articles/32685/Testing-reader-writer-locks e resume tudo muito bem. Também encontrei um novo link do MSDN sobre Compound Synchronization Objects .

Há um artigo sobre bloqueios do leitor-escritor no MSDN que apresenta algumas implementações deles. Ele também introduz o bloqueio de leitor / gravador Slim, um primitivo de synchronization do kernel apresentado com o Vista. Há também um artigo do CodeProject sobre a comparação de diferentes implementações (incluindo as do artigo do MSDN).

Os blocos de construção de threads da Intel também fornecem algumas variantes de rw_lock:

http://www.threadingbuildingblocks.org/

Eles têm um spin_rw_mutex por períodos muito curtos de contenção e um queueing_rw_mutex por períodos mais longos de contenção. O primeiro pode ser usado em código particularmente sensível ao desempenho. Este último é mais comparável em desempenho ao fornecido pela Boost.Thread ou diretamente usando pthreads. Mas perfil para se certificar de qual é uma vitória para seus padrões de access.

Boost.Thread desde o lançamento 1.35.0 já suporta bloqueios de leitor-escritor. A coisa boa sobre isso é que a implementação é muito multi-plataforma, peer-reviewed e é realmente uma implementação de referência para o próximo padrão C ++ 0x .

Eu posso recomendar a biblioteca ACE , que fornece uma infinidade de mecanismos de bloqueio e é portada para várias plataformas.

Dependendo das condições de limite do seu problema, você pode achar as seguintes classs úteis:

  • ACE_RW_Process_Mutex
  • ACE_Write_Guard e ACE_Read_Guard
  • ACE_Condition

http://www.codeproject.com/KB/threads/ReaderWriterLock.aspx

Aqui está uma boa e leve implementação adequada para a maioria das tarefas.

Classe de Bloqueio de Sincronização com Leitor Múltiplo e Um Único Escritor para Win32 por Glenn Slayde

http://www.glennslayden.com/code/win32/reader-writer-lock

C ++ 17 suporta std::shared_mutex . É suportado no MSVC ++ 2015 e 2017.

Você pode copiar o excelente ReentrantReadWriteLock da Sun. Ele inclui resources como imparcialidade opcional, dessorting de bloqueios e, é claro, reentrância.

Sim, é em Java, mas você pode facilmente ler e transpor para C ++, mesmo que você não conheça nenhum Java. A documentação que eu vinculei contém todas as propriedades comportamentais dessa implementação, para que você possa ter certeza de que faz o que deseja.

Se nada mais, é um guia.