Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Прочее";
Текущий архив: 2014.05.04;
Скачать: [xml.tar.bz2];

Вниз

СТРОКИ НЕ ПОТОКОБЕЗОПАСНЫ   Найти похожие ветки 

 
DevilDevil ©   (2013-10-31 15:54) [0]

Сегодня я получил удар в сердце. Я так долго изучал Delphi, так долго вникал во внутреннее устройство System, так часто видел Interlocked команды в подсчёте ссылок у строк, что у меня даже сомнений не было в том, что строки потокобезопасны.

Но сегодня простой тест доказал мне обратное.
И удивляет даже не факт отсутствия потокобезопасности, а нагромождение Interlocked (lock ...) команд в недрах. Минус этих команд в том, что они выполняются через трафик кеша, что достаточно сильно тормозит производительность.

Извещаю общественность об этом факте не для того, чтобы расстроить. А для того, чтобы возможно вы нашли причину, почему подсчёт ссылок в потоко небезопасных строках оправдано осуществлять через Interlocked

Вот код теста, который приводит к AccessViolation или OutOfMemory
program Project1;

{$APPTYPE CONSOLE}

uses
 Windows, SysUtils, Classes;

type
 T1 = class(TThread)
   procedure Execute; override;
 end;

 T2 = class(TThread)
   procedure Execute; override;
 end;

 T3 = class(TThread)
   procedure Execute; override;
 end;

var
 S: string;
 ThreadCounter: integer=0;

const
 ITERATIONS_COUNT = 1000000;

{ T1 }

procedure T1.Execute;
var
 i: integer;
begin
 InterlockedIncrement(ThreadCounter);

 for i := 1 to ITERATIONS_COUNT do
 begin
   S := IntToStr(i);
 end;

 InterlockedDecrement(ThreadCounter);
end;

{ T2 }

procedure T2.Execute;
var
 i: integer;
begin
 InterlockedIncrement(ThreadCounter);

 for i := 1 to ITERATIONS_COUNT do
 begin
   if (i and 1 = 1) then S := ""
   else S := IntToStr(i);
 end;

 InterlockedDecrement(ThreadCounter);
end;

{ T3 }

procedure T3.Execute;
var
 i: integer;
begin
 InterlockedIncrement(ThreadCounter);

 for i := 1 to ITERATIONS_COUNT do
 begin

 end;

 InterlockedDecrement(ThreadCounter);
end;

begin
 T1.Create(false).FreeOnTerminate := true;
 T2.Create(false).FreeOnTerminate := true;

 // wait threads
 while (true) do
 begin
   Sleep(1000);
   if (ThreadCounter = 0) then break;
 end;

 Writeln("S = "", S, """);
 Readln;
end.


 
DVM ©   (2013-10-31 15:59) [1]


>  СТРОКИ НЕ ПОТОКОБЕЗОПАСНЫ
>
> DevilDevil ©   (31.10.13 15:54) 
> Сегодня я получил удар в сердце. Я так долго изучал Delphi,
>  так долго вникал во внутреннее устройство System, так часто
> видел Interlocked команды в подсчёте ссылок у строк, что
> у меня даже сомнений не было в том, что строки потокобезопасны.
>

С чего ты взял, что строки потокобезопасны? Interlocked функции там для обеспечения потокобезопасного подсчета ссылок, но никак не содержимого строки.


 
DevilDevil ©   (2013-10-31 16:02) [2]

> DVM ©   (31.10.13 15:59) [1]
> С чего ты взял, что строки потокобезопасны?

мне казалось это логичным

> Interlocked функции там для обеспечения потокобезопасного
> подсчета ссылок, но никак не содержимого строки.


вот я и спрашиваю - зачем потокобезопасный подсчёт ссылок, если сами стороки не потокобезопасны ?


 
DVM ©   (2013-10-31 16:10) [3]


> DevilDevil ©   (31.10.13 16:02) [2]


> зачем потокобезопасный подсчёт ссылок, если сами стороки
> не потокобезопасны ?

т.е как зачем? тебе пример надо?


 
DevilDevil ©   (2013-10-31 16:11) [4]

> т.е как зачем? тебе пример надо?

если строки потоконебезопасны, то подсчёт ссылок должен быть обычным
ибо так быстрее


 
DVM ©   (2013-10-31 16:14) [5]

Допустим есть поток, которому в конструктор передают строку:

Create(const S: string);

Внутри конструктора он присваивает данную строку некому полю. В дальнейшем поток намерен изменять значение этого поля. Пока никакого копирования строки пока не выполняется, т.к. const. Дальше мы фактически имеет две ссылки (одну вне потока и одну внутри класса потока) на одну и ту же строку. Нам надо знать, что как только вне потока или внутри потока попытаются изменить строку, надо тотчас создать ее копию и каждый потом пусть работает со своей копией. Так ясно?


 
DevilDevil ©   (2013-10-31 16:21) [6]

> DVM ©   (31.10.13 16:14) [5]

okey, можешь как-то в кратце сформулировать
а) в каких условиях работы со строками потокобезопасность гарантируется
б) какие методы работы со строками ошибочны при условии доступа из разных потоков


 
Palladin ©   (2013-10-31 16:24) [7]

(facepalm)


 
DVM ©   (2013-10-31 16:25) [8]


> DevilDevil ©   (31.10.13 16:21) [6]


> б) какие методы работы со строками ошибочны при условии
> доступа из разных потоков

Если без синхронизации, то все, которые пытаются изменить содержимое строки или ее длину.


 
DevilDevil ©   (2013-10-31 16:28) [9]

> пытаются изменить содержимое строки

если говорить об изменении символа строки
то разве UniqueString() не спасёт ?


 
DVM ©   (2013-10-31 16:30) [10]


> DevilDevil ©   (31.10.13 16:21) [6]


> а) в каких условиях работы со строками потокобезопасность
> гарантируется

При изменении содержимого строки на которую указывают две ссылки по одной из ссылок


 
DVM ©   (2013-10-31 16:34) [11]


> DevilDevil ©   (31.10.13 16:28) [9]


> то разве UniqueString() не спасёт ?

UniqueString делает просто клон строки с явным копированием. После этого у нас уже не одна а две строки. Но вызов функции не атомарен, в любой момент на середине копирования работа потока может быть прервана и строку может изменить другой поток.


 
DevilDevil ©   (2013-10-31 16:40) [12]

> DVM ©

Можно ли переформулировать твои слова, сохраняя смысл, в:
Доступ к переменной типа string (AnsiString, WideString, UnicodeString) должен осуществляться только из инициализирующего или дочернего потока.

?


 
DevilDevil ©   (2013-10-31 16:41) [13]

(из дочернего при гарантии неизменности переменной)

Как-то не звучит


 
Kerk ©   (2013-10-31 16:44) [14]

Можно еще помедитировать над тем, почему у интерфейсов (а с недавнего времени и у объектов) счетчик ссылок потокобезопасен, хотя сами интерфейсы в общем случае ничего никому не гарантируют.


 
DevilDevil ©   (2013-10-31 16:44) [15]

Владеющий переменной типа string (AnsiString, WideString, UnicodeString) поток может свободно обращаться с переменной. Остальные потоки - только читать при гарантии неизмены из владеющего.

как такая формулировка ?


 
DevilDevil ©   (2013-10-31 16:45) [16]

> Kerk ©   (31.10.13 16:44) [14]
> Можно еще помедитировать над тем, почему у интерфейсов (а
> с недавнего времени и у объектов) счетчик ссылок потокобезопасен,
>  хотя сами интерфейсы в общем случае ничего никому не гарантируют.


вопрос из той же оперы, да


 
Kerk ©   (2013-10-31 16:46) [17]

Да уж.


 
DVM ©   (2013-10-31 16:53) [18]


> DevilDevil ©   (31.10.13 16:44) [15]

Что такое поток, владеющий строкой? Это она если полем объекта TThread является что ли? Тогда почему другие потоки не могут ее менять? Могут. Главное чтоб не одновременно.


 
DevilDevil ©   (2013-10-31 17:22) [19]

> DVM ©   (31.10.13 16:53) [18]

Без дополнительных средств синхронизации сложно гарантировать, что переменную никто не изменит. Я имею ввиду случаи без дополнительной синхронизации.

Владеющий поток?
Ну это не только поле TThread. Это может быть переменная на стеке или скажем элемент динамического массива.

По идее глобальной переменной владеет главный поток.
Но вполне по логике программы может быть заложено, что к какой-то переменной обращается строго один поток. Он в данном случае и называется мной "владеющим"


 
DVM ©   (2013-10-31 17:34) [20]


> По идее глобальной переменной владеет главный поток.

По-моему между областью видимости переменной и как ты говоришь "владением " нет никакой связи.

Вот про threadvar можно с уверенностью сказать что такой глобальной переменной поток владеет. К ней и доступа нет у других потоков (у каждого свой экземпляр).


> Это может быть переменная на стеке или скажем элемент динамического
> массива.

поясни


 
DevilDevil ©   (2013-10-31 18:00) [21]

> DVM ©   (31.10.13 17:34) [20]

> По-моему между областью видимости переменной и как ты говоришь
> "владением " нет никакой связи.


предположим есть глобальная переменная
var
 SecondThreadName: string;


тогда переменная попадает в область видимости всех работающих потоков. Но "владеющим" в моём понимании должен быть только один поток. Чаще у глобальной переменной владеющим будет главный поток. Но специфика переменной может быть такой, что её модификация будет привилегией какого-то дополнительно созданного потока.

> > Это может быть переменная на стеке или скажем элемент
> динамического массива.
>
> поясни


var
 S: string;
 Ar: array of string;
 Ar2: array of record
           Name: string;
           Offset: integer;
        end;
begin


 
DevilDevil ©   (2013-10-31 18:05) [22]

Я почему спросил про формулировку
Для того чтобы во-первых самому осознать
Во-вторых, достаточно долго мучает меня идея написать хак-библиотечку, ускоряющую работу со строками. При том, чтобы заложенная функциональность сохранилась: хоть в однопоточных приложениях, хоть в многопоточных.

Там используется очень много lock-команд. Даже там, где можно обойтись без них. Есть команды xchg [mem], reg который для процессора те же lock. Для однопоточных приложений вообще можно подсчёт ссылок вести без lock.

Кстати накидал небольшой тест, который порадовал своим "профитом".
program Project1;

{$APPTYPE CONSOLE}

uses
 Windows;

const
ITERATIONS_COUNT = 100000000;

var
 X: integer=0;

procedure test_winapi;
var
 i: integer;
begin
 for i := ITERATIONS_COUNT downto 1 do
 begin
   InterlockedExchange(X, i);
 end;
end;

procedure test_xchg;
asm
 mov ecx, ITERATIONS_COUNT
 @loop:
    mov edx, ecx
    xchg [offset X], edx
 dec ecx
 jnz @loop
end;

procedure test_simple;
asm
 mov ecx, ITERATIONS_COUNT
 @loop:
    mov edx, ecx

    // xchg [offset X], edx
    mov eax, [offset X]
    mov [offset X], edx
    mov edx, eax
 dec ecx
 jnz @loop
end;

type
 TProcedure = procedure();

procedure RunTest(const Name: string; const Proc: TProcedure);
var
 T: dword;
begin
 T := GetTickCount;
   Proc();
 T := GetTickCount-T;

 Writeln(Name, " = ", T, "ms");
end;

begin
 RunTest("winapi", test_winapi);
 RunTest("xchg", test_xchg);
 RunTest("simple", test_simple);

 Readln;
end.


Результат на i3:
winapi = 1107ms
xchg = 749ms
simple = 62 ms


 
Eraser ©   (2013-10-31 18:47) [23]


> DevilDevil ©

да практически все не потокобезопасно. меня это крайне удивляет, на фоне того, что число ядер растет. допустим в iOS невозможно нормально работать с графикой в доп. потоке. в VCL кстати, тоже самое, но выкрутиться можно хотя бы.


 
Sha ©   (2013-10-31 20:03) [24]

> DevilDevil ©   (31.10.13 18:05) [22]
> достаточно долго мучает меня идея написать хак-библиотечку,
> ускоряющую работу со строками

уже
http://blog.synopse.info/post/2009/12/20/Enhanced-Run-Time-library-for-Delphi-7


 
DevilDevil ©   (2013-10-31 21:42) [25]

> Eraser ©   (31.10.13 18:47) [23]

тут не в том дело, что всё потоконебезопасно
просто я ожидал потокобезопасной работы от строк

> Sha ©   (31.10.13 20:03) [24]

чёт я исходников не вижу...

P.S.
кстати с нашей последней дискуссии, после которой меня забанили
меня очень обрадовало твоё описание поразрядной сортировки. Я прям реально офигел.
но вот здесь вот у тебя большая ошибка в рассуждениях: http://guildalfa.ru/alsha/node/20
собственно, я об этом уже написал
твой VeryBadCompareInt и есть самый правильный способ сравнения


 
DVM ©   (2013-10-31 22:34) [26]


> DevilDevil ©

в скором времени в Delphi насколько я понимаю стоит ожидать появление неизменяемых (immutable) строк, которые и работают быстрее и проблема одновременного доступа из разных потоков для которых не стоит.


 
DevilDevil ©   (2013-10-31 22:51) [27]

> DVM ©   (31.10.13 22:34) [26]

хз
либо я не понимаю immutable
либо абракадбры (борланды) вновь идут по пути коммерции, а не производительности


 
DVM ©   (2013-10-31 22:56) [28]


> DevilDevil ©   (31.10.13 22:51) [27]


> либо я не понимаю immutable

Так их пока нет, понимать особенно нечего еще. А строки такие в C#, например.


 
Rouse_ ©   (2013-10-31 22:59) [29]


> DevilDevil ©   (31.10.13 21:42) [25]
> тут не в том дело, что всё потоконебезопасно
> просто я ожидал потокобезопасной работы от строк

Дим, ну зачем оно тебе?
Я ж тебе парадигму еще тогда объяснил, но ты опять похоже упираешься и пытаешься сделать свой самый быстрый манагер памяти.
Вот как ты думаешь - сколько лет прошло как появились компьютеры, а все никак не сделают что-то "прямо моментальное" :)
Да и нюансик есть -а задача то какая, что так жилы рвешь? :)

Выбор между тем когда нужно оптимизировать и где нужно оптимизировать - это целая философия, которая требует достаточно серьезной практики чтобы начать хотя-бы понимать о архитектурной необходимости самой оптимизации.

Можно конечно заоптимизировать все, но смысл, да и кто за это заплатит?


 
DevilDevil ©   (2013-10-31 23:00) [30]

> DVM ©   (31.10.13 22:56

не понимаю, за счёт чего там должен быть профит
ибо immutable, насколько я понимаю, предполагает копирование на каждый чих
а стандартный подход не предполагает


 
DevilDevil ©   (2013-10-31 23:05) [31]

> Rouse_ ©   (31.10.13 22:59) [29]

всякий нормальный человек неможет пройти мимо ситуации, когда избивают младенца
так и я не могу пройти мимо ситуации, когда вижу явные тормоза

по поводу парадигмы. Я не упёрся. Я просто перепроверил. Ибо сказанное тобой не умещалось в моей голове

по поводу что нужно оптимизировать - очень просто. Что используется часто, обрабатывает объёмы данных. К примеру строки и (даже) менеджмент памяти стоит оптимизировать. Что собственно и делали Borland/Embarcadero (копируя FastCode)

смысл и кто за это заплатит? да никто. поэтому желание есть, а мотивации нет :)))


 
DevilDevil ©   (2013-10-31 23:06) [32]

ну единственная мотивация может быть если процесс и/или результат будет интересен другим людям. а так чисто для меня - скорее всего не имеет смысла :)


 
DVM ©   (2013-10-31 23:09) [33]


> DevilDevil ©   (31.10.13 23:00) [30]


> не понимаю, за счёт чего там должен быть профит

Памяти меньше расходуется, и сравнение происходит быстрее, так как требует сравнение лишь ссылок.


 
DevilDevil ©   (2013-10-31 23:16) [34]

> DVM ©   (31.10.13 23:09) [33]

што ?
S1 := IntToStr(5);
S2 := IntToStr(5);
if (S1 <> S2) then ...


т.е. в данном случае указатели S1 и S2 равны ?
это будет аларм!
надеюсь, они проведут тестирование производительности прежде, чем пускать это в массы


 
DVM ©   (2013-10-31 23:22) [35]

Не равны. Immutable строки так не сравнивают .


 
Rouse_ ©   (2013-10-31 23:25) [36]


> DevilDevil ©   (31.10.13 23:05) [31]
> по поводу парадигмы. Я не упёрся. Я просто перепроверил.
>  Ибо сказанное тобой не умещалось в моей голове

Ну с одной стороны ты прав - все-же новые знания, а с другой, если не хочешь тратить свое время - лучше прислушиваться к тому что говорят :)


 
DVM ©   (2013-10-31 23:26) [37]

Если две строки указывают на одно и то же место в памяти то они равны, если нет, то сравнение выполняется побайтно


 
DVM ©   (2013-10-31 23:49) [38]

Кстати, в том же с# есть такая штука как интернирование строк, так вот там две строки s1 = "5" и s2 ="5" указывают на одну и ту же область памяти даже если объявлены в разных местах программы.


 
NoUser ©   (2013-11-01 00:01) [39]

Я что-то не понял, или я не понял  - зачем так много текста то.

> [0]

var
S: string;            //  ThreadString!!!  А ДЛЯ ЧЕГО ТАКОЕ НАДО ???
ThreadCounter: integer=0;
--
InterlockedIncrement(ThreadCounter);  //  молодец ! не просто ThreadCounter:=ThreadCounter+1;
--
S := IntToStr(i);  // АХТУНГ !!!


нужно ж было поискать какой-нить Interlocked_Set_String()
так как S: string; это непростой, но Pointer,
а все  > Interlocked команды в подсчёте ссылок
работают уже с Данными по этому Pointer"у

PS
В XE4 порадовали AtomicIncrement и прочие AtomicXXX функции.


 
Inovet ©   (2013-11-01 00:02) [40]

> [38] DVM ©   (31.10.13 23:49)

Ну такое можно и просто в Си++ компиляторах директивами компиляции включить, только поплохеть иногда может.



Страницы: 1 2 3 4 вся ветка

Форум: "Прочее";
Текущий архив: 2014.05.04;
Скачать: [xml.tar.bz2];

Наверх





Память: 0.57 MB
Время: 0.013 c
4-1268997623
Alex_C
2010-03-19 14:20
2014.05.04
Требование админ прав на 64-битной XP


2-1374665706
Стас258
2013-07-24 15:35
2014.05.04
Как в мемо проймать прокрутку?


4-1268893097
DremLIN.Ru
2010-03-18 09:18
2014.05.04
Как достоверно определить что программа запущена из планировщика?


15-1383748436
SergP
2013-11-06 18:33
2014.05.04
Динамический массив записей со строками. подскажите


15-1383518806
DevilDevil
2013-11-04 02:46
2014.05.04
threadvar, _GetTls, FS:tlsArray





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский