![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
Очередной баг или так и задумано [D7]
oxffff © (2007-07-29 23:26:00)Существует непустая глобальная переменная
b:Iunknown;
Представьте два потока, которые одновременно пытаются выполнить следующий код
b:=nil;
Вот непосредственно код соответствия на Asm
function _IntfClear(var Dest: IInterface): Pointer;
asm
MOV EDX,[EAX]
TEST EDX,EDX
JE @@1
MOV DWORD PTR [EAX],0
PUSH EAX
PUSH EDX
MOV EAX,[EDX]
CALL DWORD PTR [EAX] + VMTOFFSET IInterface._Release
POP EAX
@@1:
end;
Я его слегка упрощу, чтобы более легче понять его. Уберем проверку на nil и сохранение eax;
function _IntfClear(var Dest: IInterface): Pointer;
asm
MOV EDX,[EAX]
MOV DWORD PTR [EAX],0
PUSH EDX
MOV EAX,[EDX]
CALL DWORD PTR [EAX] + VMTOFFSET IInterface._Release
end;
Теперь вернемся к нашем двум потокам.
Вообразите ситуацию, когда первый поток еще не выполнил _IntfClear.
Происходит переключение на второй поток, который выполняет _IntfClear и выделяет память по потому же адресу и обнуляет ее.
Вот сериализованный поток иструкций с комментариями
procedure TForm1.Button1Click(Sender: TObject);
var a:TinterfacedObject;
begin
a:=TinterfacedObject.Create;
b:=a;
asm
push ebx; //Сохраняем ebx.
//Первый поток
mov eax,b;
push eax; //Сохранение контекста потока и переключение на второй поток.
//Второй поток
mov eax,b; //
mov b,0;
push eax;
push eax;
mov eax,[eax];
call [eax+$08];
pop eax; // Была выделена память на том же самом месте, где и объект и была очищена.
mov [eax],0;
//обратное переключение на первый поток
pop eax; //Восстановление контекста потока .
mov b,0;
push eax;
mov eax,[eax];
call [eax+$08]; // Здесь мы получаем AV.
pop ebx;//Восстанавливаем ebx
end;
b:=nil;
end;
Получение AV вызвано тем, что текущая реализация _IntfClear расчитана на один поток.
Вопрос такой
это баг. Или As designed?
oxffff (2007-07-30 13:53:00) [1]
> umbra © (30.07.07 12:56) [21]
> так может эти методы потокобезопасны :) DВ случае с IInterface,
> например, в TInterfacedObject._Release для уменьшения счетчика
> ссылок используется потокобезопасная InterlockedDecrement
И еще до самого вызова может дело не дойти.
PUSH EDX
вмешивается второй поток и 'отпускает' интерфейс shared variable. Далее выделяется память по тому же месту. И обнуляется.
переключается обратно
MOV EAX,[EDX] //теперь EAX=0
CALL DWORD PTR [EAX+VMTOFFSET Method] //Здесь AV
oxffff (2007-07-30 13:47:00) [2]
> umbra © (30.07.07 12:56) [21]
> так может эти методы потокобезопасны :) DВ случае с IInterface,
> например, в TInterfacedObject._Release для уменьшения счетчика
> ссылок используется потокобезопасная InterlockedDecrement
Вы меня не поняли.
Я имею ввиду сам interface method invoking и сама работа метода в случае shared variable небезопасен , а именно код вызова
PUSH EDX
MOV EAX,[EDX]
CALL DWORD PTR [EAX] + VMTOFFSET Method
и работа метода _Release. и указанный вами InterlockedDecrement небезопасен. По причине, того что другой поток может "отпустить" интерфейс в shared variable и разрушить объект.
Таким образом другой поток разрушил объект. а Первый еще работает с ним через тот же shared variable. А InterlockedDecrement отработает по убитому объекту. Последствия могут быть разные.
> umbra © (30.07.07 13:07) [22]
> а в Вашем примере вы вообще не имеете дела с интерфейсом,
> Вы работаете с объектом.
Так по порядку. См [0].
Global
b:Iunknown;
Local
a:TinterfacedObject;
a:=TinterfacedObject.Create;
b:=a;
...
mov eax,b;
mov b,0;
push eax;
mov eax,[eax];
call [eax+$08];
...
Так что все же это интерфейс.
umbra (2007-07-30 13:07:00) [3]а в Вашем примере вы вообще не имеете дела с интерфейсом, Вы работаете с объектом.
umbra (2007-07-30 12:56:00) [4]так может эти методы потокобезопасны :) DВ случае с IInterface, например, в TInterfacedObject._Release для уменьшения счетчика ссылок используется потокобезопасная
InterlockedDecrement
oxffff (2007-07-30 12:52:00) [5]Хотя можно усовершенствовать саму операцию присваивания, таким способом, чтобы она была бы threadSafe.
А вот другие операции требуют guard section.
;)
oxffff (2007-07-30 12:38:00) [6]
> PUSH EDX
> MOV EAX,[EDX]
> CALL DWORD PTR [EAX] + VMTOFFSET Method
> umbra © (30.07.07 12:30) [18]
> так IInterface, скорее всего и не задумывался как потокобезопасный.
> Это основа для поддержки СОМ, а там просто так ссылку на
> интерфейс в другой поток не передашь.
Да я тут хотел выдумать threadsafe работу c ref counting типами.
Только работа с ними к сожалению (точнее к счастью) не ограничивается
исключительно операциями присваивания. Об этом я что то не подумал.
:)
umbra (2007-07-30 12:30:00) [7]так IInterface, скорее всего и не задумывался как потокобезопасный. Это основа для поддержки СОМ, а там просто так ссылку на интерфейс в другой поток не передашь.
oxffff (2007-07-30 12:19:00) [8]
> Lacmus © (30.07.07 11:56) [16]
> т.е. код
>
>
> if gPtr = nil then
> gPtr := TMyClass.Create(nil)
>
>
> будет threadSafe ?
Нет. Не будет.
Т.е. для указателя выполняется код
mov [ebp-$..],eax.
С предыдущим значением нет манипуляций.
Я предлагаю сделать безопасное присваивание для string, DynArray, Interface.
А для ref counting типов выполняется еще и Counting.
Разница лишь в том, что операция изъятия и изменения значения должна быть атомарна.
Что не приведет к повторному counting для другого потока.
Хотя я тут подумал и нашел проблему посерьезней. :)
PUSH EAX
MOV EAX,[EDX]
CALL DWORD PTR [EAX] + VMTOFFSET Method
Это никак не защитишь.
Вопрос я думаю закрыт. Мое предложение некорректно.
:)
Lacmus (2007-07-30 11:56:00) [9]т.е. код
if gPtr = nil then
gPtr := TMyClass.Create(nil)
будет threadSafe ?
oxffff (2007-07-30 11:38:00) [10]
> Lacmus © (30.07.07 11:30) [14]
> >oxffff © (30.07.07 11:23) [13]
>
> Если переменная объявлена как
>
> var
> gPtr: Pointer = nil;
>
> то с ее присвоением делать ничего не надо, так ?
Именно так.
Ближе к делу. :)
Lacmus (2007-07-30 11:30:00) [11]>oxffff © (30.07.07 11:23) [13]
Если переменная объявлена как
var
gPtr: Pointer = nil;
то с ее присвоением делать ничего не надо, так ?
oxffff (2007-07-30 11:23:00) [12]
> Lacmus © (30.07.07 11:08) [12]
> >oxffff © (30.07.07 08:51) [11]
>
> Предлагаете заменить все присвоения в программе или только
> это ?
Предлагаю сделать threadSafe assignment
http://qc.codegear.com/wc/qcmain.aspx?d=49745
Все Ref Counting типы (string, DynArray, Interface).
Т.е модифицировать по аналогии _IntfClear, _IntfCopy и др.
Lacmus (2007-07-30 11:08:00) [13]>oxffff © (30.07.07 08:51) [11]
Предлагаете заменить все присвоения в программе или только это ?
oxffff (2007-07-30 08:51:00) [14]
> Речь идет об этом ?
>
> MOV DWORD PTR [EAX],0
Вот об этом
MOV EDX,[EAX]
TEST EDX,EDX
JE @@1
MOV DWORD PTR [EAX],0
Заменить либо lock cmpxсhg, либо просто xchg
Anatoly Podgoretsky (2007-07-30 00:34:00) [15]Я бы ни чему, кроме ассемблера не доверял.
jack128_ (2007-07-30 00:32:00) [16]
> Операция присваивания должна быть атомарна.
Ну..В свете того, что неявное приведение типов у нас теперь может быть перегружено, это более чем спорное утверждение.
Lacmus (2007-07-30 00:06:00) [17]>Я думаю вы не откажитесь, от того чтобы операция присваивания была атомарна?
Речь идет об этом ?
MOV DWORD PTR [EAX],0
oxffff (2007-07-29 23:58:00) [18]
> Потокозащищенным, наверно, должен быть код работы с глобальными
> переменными.
Ну не только глобальными. А с разделяемыми.
Вы конечно скажите, так это и есть защищаемый ресурс.
Только, если внимательно рассмотреть.
Операция присваивания должна быть атомарна.
И защищать ее не следуют.
Собственно спор здесь не уместен.
Я думаю вы не откажитесь, от того чтобы операция присваивания была атомарна?
Lacmus (2007-07-29 23:50:00) [19]Почему бы не продолжить :)
b: string;
b: array of ...;
Потокозащищенным, наверно, должен быть код работы с глобальными переменными.
oxffff (2007-07-29 23:50:00) [20]Для строк тоже самое. См. _LStrClr