Tenho uma variável de instância ou propriedade chamada Setting
que é compartilhada entre threads.
Uma nova instância ocasionalmente será atribuída a essa variável por outras threads.
O método que usa Setting
copiará a referência antes de funcionar, e não há problema em usar a antiga Setting
se ela estiver sendo atualizada quando o trabalho estiver prestes a começar, então o bloqueio não é necessário.
A leitura e a escrita únicas do tipo de referência são atômicas em C#, mas instruções compostas como var foo = new Foo()
essa não são atômicas e podem ser reordenadas.
Este problema é bem ilustrado no bloqueio de dupla verificação.
https://medium.com/@wangberlin2000/why-volatile-is-essential-in-double-checked-locking-singleton-0ba2906623fe
O link foi escrito em Java, mas o mesmo se aplica a C#.
https://csharpindepth.com/Articles/Singleton
Adicionado também o link para C#. Confira a "Terceira versão - tentativa de segurança de thread usando bloqueio de verificação dupla".
Gostaria de saber se é possível ler o Setting
quando seu endereço de memória é alocado, mas a inicialização do object
não é concluída.
O código de exemplo tem 3 Setting
variáveis de membro de instância, property
e virtual property
funções que atualizam o Setting
. É para fins de demonstração e o código real tem apenas uma Setting
e uma função.
- É
UpdateMemberAsync
seguro usar, já que o conteúdoGetSettingAsync
não pode vazar antes ou depois daawait
reordenação da memória? - Não é
UpdateMemberByInlinable
seguro usar porqueGetSetting
pode ser incorporado em vez de retornar o inicializadoobject
? - É
UpdateVirtualMember
seguro usar porquevirtual property
não pode ser embutido? - É
UpdateMemberByNonInlinable
seguro usar porqueApi
não pode ser embutido? - É
UpdateMemberUsingBarrier
seguro usar? - Há algo que estou esquecendo além dos exemplos fornecidos?
Adicionado o link fornecido por Peter Cordes para aqueles que buscam informações mais detalhadas. https://preshing.com/20120612/an-introduction-to-lock-free-programming/
public class Service
{
public Setting Setting = new();
public Setting SettingProperty { get; set; } = new();
public virtual Setting SettingVirtualProperty { get; set; } = new();
// Called by other threads.
public async Task StartAsync()
{
// It doesn't have to be the most recent value.
var setting = Setting;
await Foo(setting);
await Bar(setting);
}
// Api is a class with a non-virtual or sealed function.
public void UpdateMemberByInlinable(Api api)
{
Setting = api.GetSetting();
// Inlined as below.
var t = new Setting();
Setting = ptrT;
// or
SettingProperty.BackingField = ptrT;
// Set members of t after assigning the ptr.
}
// Api can be anything.
public async Task UpdateMemberAsync(Api api)
{
Setting = await api.GetSettingAsync();
// or
SettingProperty = await api.GetSettingAsync();
// or
SettingVirtualProperty = await api.GetSettingAsync();
}
// Api can be anything.
public void UpdateVirtualMember(Api api)
{
SettingVirtualProperty = api.GetSetting();
}
// Api is an interface or a class with a non-sealed virtual function.
public void UpdateMemberByNonInlinable(Api api)
{
Setting = api.GetSetting();
// or
SettingProperty = api.GetSetting();
}
// Api can be anything.
public void UpdateMemberUsingBarrier(Api api)
{
var setting = api.GetSetting();
Thread.MemoryBarrier();
Setting = setting;
// or
SettingProperty = setting;
}
}