Dans un article sur A scalable reader/writer scheme with optimistic retry il est un exemple de code:C# Est-il possible de réécrire les instructions d'un bloc finally dans try block?
using System;
using System.Threading;
public class OptimisticSynchronizer
{
private volatile int m_version1;
private volatile int m_version2;
public void BeforeWrite() {
++m_version1;
}
public void AfterWrite() {
++m_version2;
}
public ReadMark GetReadMark() {
return new ReadMark(this, m_version2);
}
public struct ReadMark
{
private OptimisticSynchronizer m_sync;
private int m_version;
internal ReadMark(OptimisticSynchronizer sync, int version) {
m_sync = sync;
m_version = version;
}
public bool IsValid {
get { return m_sync.m_version1 == m_version; }
}
}
public void DoWrite(Action writer) {
BeforeWrite();
try {
writer(); // this is inlined, method call just for example
} finally {
AfterWrite();
}
}
public T DoRead<T>(Func<T> reader) {
T value = default(T);
SpinWait sw = new SpinWait();
while (true) {
ReadMark mark = GetReadMark();
value = reader();
if (mark.IsValid) {
break;
}
sw.SpinOnce();
}
return value;
}
}
Si je fais m_version1
et m_version2
non volatile mais utilise le code:
public void DoWrite(Action writer) {
Thread.MemoryBarrier(); // always there, acquiring write lock with Interlocked method
Volatile.Write(ref m_version1, m_version1 + 1); // NB we are inside a writer lock, atomic increment is not needed
try {
writer();
} finally {
// is a barrier needed here to avoid the increment reordered with writer instructions?
// Volatile.Write(ref m_version2, m_version2 + 1); // is this needed instead of the next line?
m_version2 = m_version2 + 1; // NB we are inside a writer lock, atomic increment is not needed
Thread.MemoryBarrier(); // always there, releasing write lock with Interlocked method
}
}
Peut-instructions de la ligne m_version2 = m_version2 + 1
être réorganisées à partir de finally
en try
bloquer? Il est important qu'un écrivain finisse avant que m_version2
ne soit incrémenté.
Logiquement finally
est exécuté après try
, mais le bloc finally
n'est pas mentionné dans le list of implicit memory barriers. Il serait assez déroutant si les instructions de finally
pouvaient être déplacées avant celles de try
, mais les optimisations CPU au niveau des instructions sont toujours une magie noire pour moi. Je pourrais mettre Thread.MemoryBarrier();
avant la ligne m_version2 = m_version2 + 1
(ou utiliser Volatile.Write
), mais la question est si cela est vraiment nécessaire?
Les MemoryBarrier
présentés dans l'exemple sont implicites et générés par les méthodes Interlocked
d'un verrou d'écriture, de sorte qu'ils sont toujours présents. Le danger est qu'un lecteur puisse voir m_version2
incrémenté avant la fin de l'écrivain.
J'ai lu http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf après avoir posté ceci. Il ressemble à Volatile.Write est nécessaire, il n'y a pas de traitement spécial de 'finally' (section I.12.6-7). A moins d'avoir manqué quelque chose de très subtil à propos des CERs. –