Я сталкивался с этой реализацией одноразового шаблона, предоставляемого Microsoft: https://msdn.microsoft.com/en-us/library/system.idisposable(v=vs.110).aspx
using System;
class BaseClass : IDisposable
{
// Flag: Has Dispose already been called?
bool disposed = false;
// Public implementation of Dispose pattern callable by consumers.
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
// Protected implementation of Dispose pattern.
protected virtual void Dispose(bool disposing)
{
if (disposed)
return;
if (disposing) {
// Free any other managed objects here.
//
}
// Free any unmanaged objects here.
//
disposed = true;
}
~BaseClass()
{
Dispose(false);
}
}
Допустим, у меня есть класс C ++, связанный с этим классом C #, и я хочу delete
Объект C ++, располагающий классом C #, чтобы убедиться, что мои неуправляемые ресурсы высвобождаются правильно. Я добавляю функцию DestructNative(self)
который в основном делает родной вызов C ++ delete (CppObject*)self
на связанный объект C ++. Итак, мой код выглядит так:
// Protected implementation of Dispose pattern.
protected virtual void Dispose(bool disposing)
{
if (disposed)
return;if (disposing) {
// Free any other managed objects here.
//
}
DestructNative(self);
disposed = true;
}
Итак, мой вопрос, зная, что финализаторы C # могут быть вызваны из другого потока, нужно ли мне обеспечивать синхронизацию внутри деструктора объекта C ++, чтобы убедиться, что у меня нет условий гонки, когда Dispose(false)
вызывается из финализатора C #?
Сломан ли одноразовый шаблон microsoft? Похоже на disposed
flag — это простая переменная, которая не синхронизируется, если финализатор вызывается из другого потока.
Сломан ли одноразовый шаблон microsoft? Похоже, флаг disposed — это простая переменная, которая не синхронизируется, если финализатор вызывается из другого потока.
Нет, это не сломано.
Этот вопрос ставит 2 интересные проблемы. Для класса, который предшествует датам C++11
мышление и нить не знают, каково влияние следующего.
class PreCpp11 {
public:
int ** ptr;
bool mInitDone;
PreCpp11() : mInitDone(false) {
ptr = new int*[100];
}
init() {
for( size_t i = 0; i < 100; i++ ){
ptr[i] = new int[100];
}
mInitDone = true;
}
~PreCpp11() {
if( mInitDone ){
for( size_t i =0; i <100; i++ ){
delete ptr[i];
}
}
delete []ptr;
}
}
После кода
PreCpp11 * myObj = new PreCpp11();
myObj->init();
send_object_to_thread2( myObj );
Где поток 2 выполняет
PreCpp11 obj = get_obj_from_sync();
delete obj;
Если деструктор вызывается в другом потоке, как мы избежали гонки данных?
Учитывая это для одноразовой реализации, это вызовет гонку данных, как указано выше.
В обоих случаях я считаю, что ответ на этот код является приемлемым и совместимым. Однако он полагается на межпотоковое взаимодействие объекта PreCpp11 с тем, чтобы оно было совместимым.
Мое мышление ….
У меня есть целый ряд возможностей для гонки данных, этот поток гарантированно увидит значения, которые я записал в массив ptr, но другие потоки не гарантируют, что inter-thread happens-before
отношения произошли.
Однако, когда я связываю свой класс со вторым потоком со вторым потоком, тогда происходит синхронизация, которая обеспечивает правильную синхронизацию моего указателя между исходным потоком и потоком «удаления», создает inter-thread happens-before
отношения, которые с учетом этого происходят после того, как я позвонил init
что значения, увиденные в потоке 2, являются значениями, которые видел поток 1, когда он начал взаимодействовать со вторым потоком.
Таким образом, если поток 1 продолжает модифицировать объект после того, как он был передан потоку 2, тогда могут происходить гонки данных, но при условии, что связь между потоками соответствует, тогда второй поток видит первое поведение.
Sequenced-перед тем
->init() is sequenced before
send_object_to_thread2( myObj );
Бывает раньше
->init() happens before the synchronized communication with thread2.
Меж-поток происходит раньше
->init() happens before thread 2 gets the data and calls the destructor
->init() is sequenced-before the synchronized write to the inter-thread communication, and the write occurs before the synchronized read.
The actual write-read is ordered, as they are synchronized.
Таким образом, до тех пор, пока взаимодействие объекта между потоками синхронизировано и дальнейшая модификация объекта не происходит после передачи в новый поток, гонки данных не происходит.
Других решений пока нет …