O C ++-faq é geralmente administrado pela comunidade C ++, e você poderia gentilmente nos pedir opiniões em nosso chat.
Cachorro
@DeadMG: Eu não conhecia o C ++ - faq e sua etiqueta, foi sugerido em um comentário.
K-ballo
2
Onde você ouviu que const significa thread-safe?
Mark B
2
@Mark B: Herb Sutter e Bjarne Stroustrup estavam dizendo isso na Standard C ++ Foundation , veja o link na parte inferior da resposta.
K-ballo
NOTA PARA OS QUE VÊM AQUI: a verdadeira questão NÃO é se constsignifica thread-safe. Isso seria um absurdo, pois caso contrário ele iria dizer que você deve ser capaz de ir em frente e marcar todos os métodos thread-safe como const. Em vez disso, a pergunta que estamos realmente fazendo é constIMPLIES thread-safe, e é sobre isso que trata esta discussão.
user541686
Respostas:
131
Ouvi dizer que isso constsignifica thread-safe em C ++ 11 . Isso é verdade?
É um pouco verdade ...
Isso é o que a linguagem padrão tem a dizer sobre segurança de thread:
[1.10 / 4]
Duas avaliações de expressão entram em conflito se uma delas modifica uma localização de memória (1.7) e a outra acessa ou modifica a mesma localização de memória.
[1.10 / 21]
A execução de um programa contém uma corrida de dados se contiver duas ações conflitantes em threads diferentes, pelo menos uma das quais não é atômica e nenhuma ocorre antes da outra. Qualquer corrida de dados resulta em um comportamento indefinido.
que nada mais é do que a condição suficiente para que ocorra uma corrida de dados :
Existem duas ou mais ações sendo realizadas ao mesmo tempo em uma determinada coisa; e
Pelo menos um deles é uma escrita.
A Biblioteca Padrão se baseia nisso, indo um pouco mais longe:
[17.6.5.9/1]
Esta seção especifica os requisitos que as implementações devem atender para evitar data races (1.10). Cada função de biblioteca padrão deve atender a cada requisito, a menos que especificado de outra forma. As implementações podem evitar corridas de dados em casos diferentes dos especificados abaixo.
[17.6.5.9/3]
Uma função de biblioteca padrão C ++ não deve modificar direta ou indiretamente objetos (1.10) acessíveis por threads diferentes da thread atual, a menos que os objetos sejam acessados direta ou indiretamente por meio dosargumentosnão constantes da função, incluindothis.
que, em palavras simples, diz que espera que as operações em constobjetos sejam seguras com thread . Isso significa que a Biblioteca Padrão não introduzirá uma corrida de dados, desde que as operações em constobjetos de seus próprios tipos também
Consistem inteiramente em leituras - isto é, não há gravações--; ou
Sincroniza gravações internamente.
Se essa expectativa não for válida para um de seus tipos, usá-lo direta ou indiretamente junto com qualquer componente da Biblioteca Padrão pode resultar em uma disputa de dados . Em conclusão, constsignifica thread-safe do ponto de vista da Biblioteca Padrão . É importante notar que este é apenas um contrato e não será executado pelo compilador, se você quebrá-lo obterá um comportamento indefinido e estará por conta própria. Se constestá presente ou não, não afetará a geração de código - pelo menos não no que diz respeito a corridas de dados -.
Que isso significa consté agora o equivalente de Java s' synchronized?
Não . De modo nenhum...
Considere a seguinte classe excessivamente simplificada que representa um retângulo:
A função de membroarea é segura para threads ; não porque é const, mas porque consiste inteiramente em operações de leitura. Não há gravações envolvidas e pelo menos uma gravação envolvida é necessária para que ocorra uma disputa de dados . Isso significa que você pode chamar areade quantos threads desejar e obterá resultados corretos o tempo todo.
Observe que isso não significa que rectseja seguro para threads . Na verdade, é fácil ver como se uma chamada para areaacontecesse ao mesmo tempo que uma chamada para set_sizeem um dado rect, então areapoderia acabar computando seu resultado com base em uma largura antiga e uma nova altura (ou mesmo em valores truncados) .
Mas está tudo bem, rectnão é constnem esperado que seja thread-safe . Um objeto declarado const rect, por outro lado, seria thread-safe, já que nenhuma escrita é possível (e se você está considerando const_cast-ing algo declarado originalmente, constentão você obterá um comportamento indefinido e pronto ).
Então, o que isso significa?
Vamos supor - para fins de argumentação - que as operações de multiplicação são extremamente caras e é melhor evitá-las quando possível. Poderíamos calcular a área apenas se fosse solicitada e, em seguida, armazená-la em cache caso seja solicitada novamente no futuro:
[Se este exemplo parecer muito artificial, você poderia substituir mentalmente intpor um número inteiro alocado dinamicamente muito grande que é inerentemente não seguro para thread e para o qual as multiplicações são extremamente caras.]
A função de membroarea não é mais segura para thread , ela está fazendo gravações agora e não está sincronizada internamente. Isso é um problema? A chamada para areapode acontecer como parte de um construtor de cópia de outro objeto, tal construtor pode ter sido chamado por alguma operação em um contêiner padrão e, nesse ponto, a biblioteca padrão espera que essa operação se comporte como uma leitura em relação às corridas de dados . Mas estamos escrevendo!
Assim que colocarmos um rectem um container padrão - direta ou indiretamente - estaremos firmando um contrato com a Biblioteca Padrão . Para continuar fazendo gravações em uma constfunção e ainda honrando esse contrato, precisamos sincronizar internamente essas gravações:
Observe que tornamos a areafunção thread-safe , mas ela rectainda não é thread-safe . Uma chamada para areaacontecer ao mesmo tempo que uma chamada para set_sizeainda pode acabar computando o valor errado, uma vez que as atribuições para widthe heightnão são protegidas pelo mutex.
Se realmente quiséssemos um thread-saferect , usaríamos uma primitiva de sincronização para proteger o não-thread-saferect .
Eles estão ficando sem palavras-chave ?
Sim, eles estão. Eles estão ficando sem palavras-chave desde o primeiro dia.
@Ben Voigt: É meu entendimento que a especificação do C ++ 11 para std::stringestá redigida de uma forma que já proíbe o COW . Não me lembro dos detalhes ...
K-ballo
3
@BenVoigt: Não. Isso simplesmente evitaria que tais coisas não fossem sincronizadas - ou seja, não seguras para thread. C ++ 11 já bane COW explicitamente - esta passagem em particular não tem nada a ver com isso, entretanto, e não baniria COW.
Cachorro
2
Parece-me que existe uma lacuna lógica. [17.6.5.9/3] proíbe "demais", dizendo "não deve modificar direta ou indiretamente"; deve dizer "não deve, direta ou indiretamente, introduzir uma corrida de dados", a menos que uma gravação atômica esteja em algum lugar definida para não ser uma "modificação". Mas não consigo encontrar isso em lugar nenhum.
Andy Prowl
1
Provavelmente deixei todo o meu ponto um pouco mais claro aqui: isocpp.org/blog/2012/12/… Obrigado por tentar ajudar de qualquer maneira.
Andy Prowl
1
às vezes me pergunto quem foi o único (ou os diretamente envolvidos) realmente responsável por escrever alguns parágrafos padrão como esses.
const
significa thread-safe. Isso seria um absurdo, pois caso contrário ele iria dizer que você deve ser capaz de ir em frente e marcar todos os métodos thread-safe comoconst
. Em vez disso, a pergunta que estamos realmente fazendo éconst
IMPLIES thread-safe, e é sobre isso que trata esta discussão.Respostas:
É um pouco verdade ...
Isso é o que a linguagem padrão tem a dizer sobre segurança de thread:
que nada mais é do que a condição suficiente para que ocorra uma corrida de dados :
A Biblioteca Padrão se baseia nisso, indo um pouco mais longe:
que, em palavras simples, diz que espera que as operações em
const
objetos sejam seguras com thread . Isso significa que a Biblioteca Padrão não introduzirá uma corrida de dados, desde que as operações emconst
objetos de seus próprios tipos tambémSe essa expectativa não for válida para um de seus tipos, usá-lo direta ou indiretamente junto com qualquer componente da Biblioteca Padrão pode resultar em uma disputa de dados . Em conclusão,
const
significa thread-safe do ponto de vista da Biblioteca Padrão . É importante notar que este é apenas um contrato e não será executado pelo compilador, se você quebrá-lo obterá um comportamento indefinido e estará por conta própria. Seconst
está presente ou não, não afetará a geração de código - pelo menos não no que diz respeito a corridas de dados -.Não . De modo nenhum...
Considere a seguinte classe excessivamente simplificada que representa um retângulo:
A função de membro
area
é segura para threads ; não porque éconst
, mas porque consiste inteiramente em operações de leitura. Não há gravações envolvidas e pelo menos uma gravação envolvida é necessária para que ocorra uma disputa de dados . Isso significa que você pode chamararea
de quantos threads desejar e obterá resultados corretos o tempo todo.Observe que isso não significa que
rect
seja seguro para threads . Na verdade, é fácil ver como se uma chamada paraarea
acontecesse ao mesmo tempo que uma chamada paraset_size
em um dadorect
, entãoarea
poderia acabar computando seu resultado com base em uma largura antiga e uma nova altura (ou mesmo em valores truncados) .Mas está tudo bem,
rect
não éconst
nem esperado que seja thread-safe . Um objeto declaradoconst rect
, por outro lado, seria thread-safe, já que nenhuma escrita é possível (e se você está considerandoconst_cast
-ing algo declarado originalmente,const
então você obterá um comportamento indefinido e pronto ).Vamos supor - para fins de argumentação - que as operações de multiplicação são extremamente caras e é melhor evitá-las quando possível. Poderíamos calcular a área apenas se fosse solicitada e, em seguida, armazená-la em cache caso seja solicitada novamente no futuro:
[Se este exemplo parecer muito artificial, você poderia substituir mentalmente
int
por um número inteiro alocado dinamicamente muito grande que é inerentemente não seguro para thread e para o qual as multiplicações são extremamente caras.]A função de membro
area
não é mais segura para thread , ela está fazendo gravações agora e não está sincronizada internamente. Isso é um problema? A chamada paraarea
pode acontecer como parte de um construtor de cópia de outro objeto, tal construtor pode ter sido chamado por alguma operação em um contêiner padrão e, nesse ponto, a biblioteca padrão espera que essa operação se comporte como uma leitura em relação às corridas de dados . Mas estamos escrevendo!Assim que colocarmos um
rect
em um container padrão - direta ou indiretamente - estaremos firmando um contrato com a Biblioteca Padrão . Para continuar fazendo gravações em umaconst
função e ainda honrando esse contrato, precisamos sincronizar internamente essas gravações:Observe que tornamos a
area
função thread-safe , mas elarect
ainda não é thread-safe . Uma chamada paraarea
acontecer ao mesmo tempo que uma chamada paraset_size
ainda pode acabar computando o valor errado, uma vez que as atribuições parawidth
eheight
não são protegidas pelo mutex.Se realmente quiséssemos um thread-safe
rect
, usaríamos uma primitiva de sincronização para proteger o não-thread-saferect
.Sim, eles estão. Eles estão ficando sem palavras-chave desde o primeiro dia.
Fonte : Você não sabe
const
emutable
- Herb Sutterfonte
std::string
está redigida de uma forma que já proíbe o COW . Não me lembro dos detalhes ...