É inteligente substituir boost :: thread e boost :: mutex por equivalentes c ++ 11?

153

Motivação: o motivo pelo qual estou considerando é que meu gerente de projeto genial acha que o impulso é outra dependência e que é horrível porque "você depende dele" (tentei explicar a qualidade do impulso e desisti depois de algum tempo :( A menor razão pela qual eu gostaria de fazer isso é que eu gostaria de aprender os recursos do c ++ 11, porque as pessoas começarão a escrever código nele.

  1. Existe um mapeamento 1: 1 entre #include<thread> #include<mutex>e equivalentes de impulso?
  2. Você consideraria uma boa idéia substituir o material de impulso pelo material do c ++ 11
    . Meu uso é primitivo, mas há exemplos em que o std não oferece o que o impulso faz? Ou (blasfêmia) vice-versa?

PS Eu uso o GCC para que os cabeçalhos estejam lá.

NoSenseEtAl
fonte
46
As diretrizes de codificação do Google da IMO são estúpidas de várias maneiras ... Por exemplo. Eles não permitem auto de C ++ 11 ... :)
NoSenseEtAl
5
Diretrizes de citação: [automático] dificulta a legibilidade [porque remove] a redundância verificada (como nomes de tipo) que pode ser útil para os leitores.
Andrew Tomazos 16/03/12
31
Para (auto it = v.begin () ... :)
NoSenseEtAl
15
@ AndrewTomazos-Fathomling: Sério? Pessoalmente, acho que nunca me importei com o tipo real do iterador (bem, talvez algumas vezes), apenas as operações suportadas ... Eu diria que a redundância sintática raramente é uma boa idéia (DRY).
Grizzly
16
btw google modificou suas diretrizes mudos agora finalmente eles permitem auto
NoSenseEtAl

Respostas:

192

Existem várias diferenças entre o Boost.Thread e a biblioteca de threads padrão do C ++ 11:

  • O Boost suporta cancelamento de encadeamento, os encadeamentos C ++ 11 não
  • O C ++ 11 suporta std::async, mas o Boost não
  • O Boost possui um boost::shared_mutexbloqueio para múltiplos leitores / gravador único. O análogo std::shared_timed_mutexestá disponível apenas desde C ++ 14 ( N3891 ), enquanto std::shared_mutexestá disponível apenas desde C ++ 17 ( N4508 ).
  • Os tempos limite do C ++ 11 são diferentes dos tempos limite do Boost (embora isso deva mudar em breve agora, o Boost.Chrono foi aceito).
  • Alguns dos nomes são diferentes (por exemplo, boost::unique_futurevs std::future)
  • A semântica de passagem de argumentos de std::threadé diferente de boost::thread--- Boost usa boost::bind, o que requer argumentos copiáveis. std::threadpermite que tipos somente de movimentação std::unique_ptrsejam passados ​​como argumentos. Devido ao uso de boost::bind, a semântica de espaços reservados, como _1em expressões de ligação aninhadas, também pode ser diferente.
  • Se você não chamar explicitamente join()ou detach()o boost::threadoperador destruidor e de atribuição chamará detach()o objeto de thread que está sendo destruído / atribuído. Com um std::threadobjeto C ++ 11 , isso resultará em uma chamada std::terminate()e abortará o aplicativo.

Para esclarecer o ponto sobre os parâmetros somente de movimentação, o C ++ 11 válido é válido e transfere a propriedade do parâmetro inttemporário std::unique_ptrpara o de f1quando o novo encadeamento é iniciado. No entanto, se você usar boost::thread, ele não funcionará, pois é usado boost::bindinternamente e std::unique_ptrnão pode ser copiado. Também existe um bug na biblioteca de threads C ++ 11 fornecida com o GCC que impede esse trabalho, como também é usado std::bindna implementação.

void f1(std::unique_ptr<int>);
std::thread t1(f1,std::unique_ptr<int>(new int(42)));

Se você estiver usando o Boost, provavelmente poderá alternar para os threads do C ++ 11 de maneira relativamente indolor, se o seu compilador suportar (por exemplo, versões recentes do GCC no linux têm uma implementação quase completa da biblioteca de threads do C ++ 11 disponível no -std=c++0xmodo).

Se o seu compilador não suportar threads C ++ 11, você poderá obter uma implementação de terceiros como Just :: Thread , mas isso ainda é uma dependência.

Anthony Williams
fonte
1
Existem métodos separados de bloqueio / desbloqueio para leitores e gravadores ( lock/ unlockpara gravadores versus 'lock_shared / unlock_shared' para leitores). Vários leitores podem chamar o lock_shared sem bloquear, desde que nenhum gravador o esteja usando.
Dave S
2
Os shared_mutexdocumentos estão em boost.org/doc/libs/1_47_0/doc/html/thread/… . Você bloqueia o mutex como compartilhado ou exclusivo e, em seguida, usa a função de desbloqueio correspondente. Você também pode usar os tipos RAII para fazer isso ( shared_lockusa um bloqueio de leitura compartilhado lock_guarde unique_lockum bloqueio exclusivo). Tentei esclarecer o ponto sobre os tipos somente de movimento.
Anthony Williams
3
Mais uma coisa menor que me atrapalhou: no boost, o destruidor de um encadeamento em execução o desanexa ( boost.org/doc/libs/1_47_0/doc/html/thread/… ), enquanto em C ++, o destruidor de um encadeamento em execução termina com as chamadas () (FDIS 30.3.1.3)
Cubbi 30/08
3
No C ++ 11, a try_scoped_lockfuncionalidade é coberta por std::unique_lock. Existe um construtor que pega um mutex e std::try_to_lock, em seguida, chama try_lock()o mutex em vez de lock(). Veja stdthread.co.uk/doc/headers/mutex/unique_lock/…
Anthony Williams
4
Sim, o Boost.Thread se tornou muito mais próximo do padrão C ++ 11 desde que escrevi isso, principalmente devido ao trabalho de Vicente Botet.
Anthony Williams
24

std::threadé amplamente modelado depois boost::thread, com algumas diferenças :

  • a semântica não copiável, de uma alça-mapeia-para-um-OS-semântica, é mantida. Mas esse encadeamento é móvel para permitir o retorno do encadeamento das funções de fábrica e a colocação em contêineres.
  • Esta proposta acrescenta cancelamento ao boost::thread, o que é uma complicação significativa. Essa alteração tem um grande impacto não apenas no encadeamento, mas também no restante da biblioteca de threads C ++. Acredita-se que essa grande mudança seja justificável por causa do benefício.
    • O destruidor de encadeamentos agora deve chamar cancel antes de desanexar para evitar vazamentos acidental de encadeamentos filhos quando os encadeamentos pai forem cancelados.
    • Um membro de desanexação explícito agora é necessário para ativar a desanexação sem cancelar.
  • Os conceitos de identificador de segmento e identidade de segmento foram separados em duas classes (eles são da mesma classe boost::thread). Isso é para facilitar a manipulação e o armazenamento da identidade do encadeamento.
  • A capacidade de criar um ID de encadeamento com garantia de comparação igual a nenhum outro encadernável juntável foi adicionada ( boost::threadnão possui isso). Isso é útil para o código que deseja saber se está sendo executado pelo mesmo encadeamento de uma chamada anterior (mutexes recursivos são um exemplo concreto).
  • Existe uma "porta dos fundos" para obter o identificador de encadeamento nativo, para que os clientes possam manipular encadeamentos usando o SO subjacente, se desejar.

Como é de 2007, alguns pontos não são mais válidos: agora boost::threadtem uma native_handlefunção e, como comentam os comentaristas, std::threadnão tem mais cancelamento.

Não encontrei diferenças significativas entre boost::mutexe std::mutex.

Alex B
fonte
2
std::threadnão tem cancelamento; é isso boost::threadque faz!
Anthony Williams
@ Anthony tem certeza de que não quer interrupt()aumentar o impulso :: thread? Também parece que é uma proposta original, o que mudou desde 2007.
Alex B
4
Sim, o cancelamento no aumento é chamado de "interrupção". Sim, esta é uma proposta antiga. O rascunho público mais recente do padrão C ++ 11 (que inclui a biblioteca de threads) é open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3242.pdf
Anthony Williams em
6

Há um motivo para não migrar para std::thread.

Se você estiver usando vinculação estática, std::threadficará inutilizável devido a esses bugs / recursos do gcc:

Ou seja, se você ligar std::thread::detachou std::thread::joinisso levará a uma exceção ou falha, enquanto boost::threadfunciona bem nesses casos.

ks1322
fonte
Vejo que um erro não está confirmado e o outro é inválido, com um comentário dizendo que o repórter deveria estar vinculado libpthread.a. Você tem certeza absoluta do que está dizendo?
einpoklum
1
@einpoklum, você poderá fazê-lo funcionar Wl,--whole-archive -lpthread -Wl,--no-whole-archive, consulte esta resposta, por exemplo, stackoverflow.com/a/23504509/72178 . Mas não é uma maneira muito direta de se conectar libpthread.ae também é considerada uma má ideia.
ks1322
4
Podemos supor que esses bugs foram corrigidos, pois agora é 2016? Os erros foram publicados em 2012 e, a partir do gcc 4.9.2, ele suporta oficialmente o C ++ 11, portanto não podemos reclamar do C ++ 11 antes do suporte oficial.
Splash
6

Caso da empresa

Se você está escrevendo um software para a empresa que precisa ser executado em uma variedade de sistemas operacionais de moderada a grande e, consequentemente, compilado com uma variedade de compiladores e versões de compilador (especialmente os relativamente antigos) nesses sistemas operacionais, minha sugestão é ficar longe de C ++ 11 completamente por enquanto. Isso significa que você não pode usar std::thread, e eu recomendaria usar boost::thread.

Caso de Inicialização Básico / Técnico

Se você está escrevendo para um ou dois sistemas operacionais, sabe com certeza que só precisará construir com um compilador moderno que suporte principalmente C ++ 11 (por exemplo, VS2015, GCC 5.3, Xcode 7), e você ainda não está dependente da biblioteca de impulso, std::threadpode ser uma boa opção.

Minha experiência

Pessoalmente, sou parcial em relação a bibliotecas robustas, usadas, altamente compatíveis e altamente consistentes, como impulso versus uma alternativa muito moderna. Isto é especialmente verdade para assuntos de programação complicados, como threading. Além disso, há muito tempo tenho tido grande sucesso com boost::thread(e otimização em geral) em uma vasta gama de ambientes, compiladores, modelos de encadeamento etc. Quando é minha opção, escolho a otimização.

Nicholas Smith
fonte
1
@UmNyobe Ele está certo. Muitas implementações do encadeamento do C ++ 11 são tão quebradas que fico surpreso que as pessoas considerem usá-lo.
StaceyGirl #
3

Com o Visual Studio 2013, o std::mutexcomportamento parece diferente do que o boost::mutex, o que me causou alguns problemas (consulte esta pergunta ).

Robert Hegner
fonte
1

Com relação ao std :: shared_mutex adicionado no C ++ 17

As outras respostas aqui fornecem uma visão geral muito boa das diferenças em geral. No entanto, existem vários problemas com std::shared_mutexesse impulso resolvido.

  1. Mutices atualizáveis. Estes estão ausentes std::thread. Eles permitem que um leitor seja atualizado para um escritor sem permitir que outros escritores entrem antes de você . Isso permite que você faça coisas como pré-processar uma computação grande (por exemplo, reindexar uma estrutura de dados) quando estiver no modo de leitura e, em seguida, atualizar para gravar para aplicar a reindexação, mantendo apenas o bloqueio de gravação por um curto período de tempo.

  2. Justiça. Se você tiver atividade constante de leitura com a std::shared_mutex, seus escritores serão bloqueados indefinidamente. Isso ocorre porque, se outro leitor aparecer, ele sempre terá prioridade. Com boost:shared_mutex, todos os threads eventualmente terão prioridade. (1) Nem leitores nem escritores passarão fome.

O principal é que, se você tiver um sistema de alto rendimento, sem tempo de inatividade e contenção muito alta, std::shared_mutexnunca funcionará para você sem criar manualmente um sistema prioritário sobre ele. boost::shared_mutexfuncionará imediatamente, embora seja necessário mexer com ele em alguns casos. Eu argumentaria que std::shared_mutexo comportamento é um bug latente que está esperando para acontecer na maioria dos códigos que o usam.

(1) O algoritmo real usado é baseado no agendador de threads do SO. Na minha experiência, quando as leituras estão saturadas, há pausas mais longas (ao obter um bloqueio de gravação) no Windows do que no OSX / Linux.

Robert Fraser
fonte
0

Tentei usar o shared_ptr do std em vez do boost e encontrei um bug na implementação do gcc dessa classe. Meu aplicativo estava travando por causa do destruidor chamado duas vezes (essa classe deve ser segura para threads e não deve gerar esses problemas). Depois de passar para boost :: shared_ptr, todos os problemas desapareceram. As implementações atuais do C ++ 11 ainda não estão maduras.

O Boost também possui mais recursos. Por exemplo, o cabeçalho na versão std não fornece serializador para um fluxo (ou seja, cout << duration). O Boost possui muitas bibliotecas que usam seus próprios equivalentes etc., mas não cooperam com as versões std.

Para resumir - se você já possui um aplicativo escrito usando boost, é mais seguro manter seu código como está, em vez de se esforçar para mudar para o padrão C ++ 11.

user3323559
fonte
4
O shared_ptrdestruidor não precisa ser seguro para threads, é um comportamento indefinido ter um thread acessando um objeto enquanto outro thread o está destruindo. Se você acha que encontrou um bug no shared_ptr do GCC, informe -o , caso contrário, quanto à probabilidade de usá-lo errado.
Jonathan Wakely