Форум на Kuban.ru (http://forums.kuban.ru/)
-   Разработка программ (http://forums.kuban.ru/f1024/)
-   -   ОФФ/4: INC(I), что сложного? Почему так сложно реализовали? (http://forums.kuban.ru/f1024/off_4_inc_i_chto_slozhnogo_pochemu_tak_slozhno_realizovali-3711924.html)

ELEA 16.02.2013 00:32

ОФФ/4: INC(I), что сложного? Почему так сложно реализовали?
 
Delphi (думаю любой версии)
Имеем массу одновременных потоков, к примеру 30 шт.
В каждом свой цикл в 1000 раз.
В кажтом цикле есть inc(i) - i это глобальная переменная.

По завершении каждого потока, который гарантировано вызывает inc(i) 1000 раз, в i почемуто значение меньшее чем 30000! Может быть 29456, может 29211 и т.д.
Вобщем inc(i) не просто инкрементирует, а чтото кудато запооминает, там инкрементирует и назад кладет. Ну и ессно без синхронизации или крит. секций такая простая штука не дает карантированного результата.

ёпрст...

maxxx 16.02.2013 08:48

0-ELEA > В замечательном языке С++ для этого есть ключевое слово [b]volatile[/b], а в Windows API семейство функций [b]InterlockedXXX[/b].

ЗЫ: Паскаль ацтой, С++ 2011 в народ! :))

Том 16.02.2013 09:14

Может туда прикрутить какой-нибудь EnterCriticalSession ? :-)
А то пока один поток делает...
[em]Push ebp
mov ebp,esp
enter 15,2[/em] (или как там у них)
...другой уже досчитал и сохраняет результат.

NTFS_ 16.02.2013 10:18

Все правильно. Потоки выполняются одновременно. Если в каждом потоке две команды A и B, то возможно, что порядок выполнения для двух потоков будет таков:

A из потока 1
A из потока 2
B из потока 1
B из потока 2

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

Что же касается любимого мною Delphi, то многопоточное программирование никогда не было его сильной стороной, ИМХО. Тот же TThread, главным образом, нужен для того, что вынести в поток тяжелую операцию, не "повесив" при этом интерфейс.

40KHYTbIU 16.02.2013 10:24

ИМХО подход неверный. Если будете использовать блокирующие функции будут ожидания в потоках. Попробуйте реализовать через очередь.

wayerr 16.02.2013 12:41

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

в то время как в нормальных языках давно есть AtomicInteger и подобные

Mikle Quits 16.02.2013 14:11

[quote=NTFS_;29084362]Потому любая операция, которая занимает более одной команды ассемблера, должна оформляться специальным образом.[/quote]
Я так понял, топикстартер и ждёт, что это будет одна ассемблерная команда inc.
[quote=ELEA;29082603]не просто инкрементирует, а чтото кудато запооминает, там инкрементирует и назад кладет[/quote]
Всё верно, это оптимизация - компилятор видит, что переменная нигде не используется, её выгоднее инкрементировать в регистре, чем в ячейке памяти, того, что переменная может быть задействована в другом потоке, компилятор не предполагает. Попробуй, ради интереса, инкрементировать переменную в асм вставке, тогда оптимизатор не вмешается.

TVV1 16.02.2013 15:46

to0 Так с потоками не работают ;) Как ты написал так вот и работает, т.е. если нет синхронизации доступа к глобальным данным то и нечего удивляться такому результату.

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

to1 volatile в общем случае ничего не гарантирует, а лишь говорит компилятору что значение переменной может быть изменено из вне и при работе с этой переменной нельзя использовать оптимизации. И не более, и еще то как это должно быть сделано вроде не прописано в стандарте и по этому поведение при volatile еще будет зависеть от того как это сделает компилятор.

ELEA 16.02.2013 17:09

1-Максыч > обязательно ктонить скажет такое. Ну не могут не сказать: твой выбор гавно, на дворе другое время и номальные люди выбирают другое.

TVV1 16.02.2013 19:10

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

А delphi к сожалению сдал позиции, не смотря на потуги эмбаркодеро.

maxxx 16.02.2013 19:33

7-TVV1 > to1 [em]volatile в общем случае ничего не гарантирует, а лишь говорит компилятору что значение переменной может быть изменено из вне и при работе с этой переменной нельзя использовать оптимизации[/em] - мбугого, еще перлов в студию! :)) Оно как раз и [b]гарантирует[/b] то, что "при работе с этой переменной нельзя использовать оптимизации", а это и есть одна из проблем у автора.
8-ELEA >
Чего тебе не так? Я тебе лично что-то сказал? Я тебе даже функции назвал, которыми пользоваться надо, а ты еще и бочку на меня катишь. Странный ты человек. :(

ELEA 16.02.2013 22:35

6-Mikle Quits > в XE2 asm убрали... :(

10-Максыч > Да дело в том, что вопрос небыл как исправить, или как решить. Я и сам умею решать такие проблемки... И даже небыл вопрос - почему делфи мертв или както так.
Мне интересно, почему столь простую функцию/процедуру реализовали не столь просто.
а в Си - ++i так же себя ведет?

ELEA 16.02.2013 22:36

6-Mikle Quits > " что переменная нигде не используется, её выгоднее инкрементировать в регистре, чем в ячейке памяти" - спасибо за мысль. Похоже, что истина гдето именно в этом.

TVV1 17.02.2013 01:53

>а в Си - ++i так же себя ведет?
А почему бы и нет :)
Тут ведь несколько действий: считать значение, увеличить на единицу, записать значение. И по скольку эти действа происходят в параллельно выполняющихся потоках, то отсюда все фокусы.
Вот, например, пусть есть два потока и глобальная переменная i=0.
Первый поток читает значение i, увеличивает его на единицу, но не успевает это дело записать обратно и его квант заканчивается. Стартует второй поток читает значение i видит что оно равно нулю, увеличивает его на 1 и записывает результат обратно, т.е. i=1.
Затем квант получает второй поток и записывает в i свою 1. И того в i вместо 2 будет 1.

maxxx 17.02.2013 06:31

11-ELEA > В С++ некоторые отважные производители компиляторов даже взяли на себя ответственность и за разруливание атомарных операций с целыми при использовании volatile (вот честно - я бы завязывался на компиляторе иногда, и не тупил про "кроссплатформенность", когда какой-то строптивый олень не захотел реализовать параметр [b]once[/b] для директивы [b]#pragma[/b] . Потому что хорошие вещи становятся стандартом обычно де-факто). Да, и еще я думаю, что производители хороших компиляторов С++ получше смогут разрулить такую ситуацию. А ответ на твой вопрос "почему" был где-то в моем посте №1, но ты почему-то обиделсо.

Кстати, а как ты решил столь страшную проблему?
12-ELEA > Ахринеть, а Сишники об этом с рождения знают. :)13-TVV1 > Откуда поток берет, и куда не успевает записать?

XentaAbsenta 23.02.2013 21:13

1-Максыч > Это заблуждение.
volatile в C++ гарантирует volatile_write, но не гарантирует volatile_read (так же как и в C#), а потому возможны гонки первого рода, к тому же инкремент - не атомарная операция (собственно поэтому от синронизации никуда не уйти, InterlockedXXX - тоже функции синхронизации).

при использовании InterlockedXXX помечать переменную как volatile не обязательно.

XentaAbsenta 23.02.2013 21:15

насколько мне известно, volatile правильно работает только в JVM 1.5

XentaAbsenta 23.02.2013 21:17

да, маленькое уточнение: разговор идёт об x86

maxxx 02.03.2013 01:46

15-Философ > я только за С++ говорил, если Вы (ты) не заметили.
Ну и маленькое уточнение - [em]"при использовании InterlockedXXX помечать переменную как volatile не обязательно."[/em]
[url]http://msdn.microsoft.com/ru-RU/library/windows/desktop/ms683614(v=vs.85).aspx[/url]
А "ОНИ" думают по-другому. Ай-яй-яй.

XentaAbsenta 03.03.2013 23:23

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

вне зависимости от волатильности переменной

(невозможно получить указатель на регистр процессора)

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

#include "stdafx.h"
#include "Windows.h"

int _tmain(int argc, _TCHAR* argv[])
**
LONG ii = 0;
InterlockedIncrement(*ii);
return 0;
**

maxxx 06.03.2013 20:57

На чем компилируется, забыл уточнить?

maxxx 06.03.2013 21:06

20-Максыч > Судя по _tmain, студия.
Вот только какая, О_о?? Версии 1.0 alpha? Как она съела разыменование переменной типа LONG, ужаснах..
Про то, что она без приведения типа не скомпилировала бы и неявное приведение nonvolatile к volatile-указателю - я уже молчу.

maxxx 06.03.2013 21:11

+21, точнее, в данном случае может бы и съела. Но это уже "умный" компилятор, а вообще, рассуждать вот так, и, что еще хуже - пользоваться таким в жизни:

[em]если вспомнить во что компилируется код с волатильными переменными, то логично предполагать, что указатель в любом случае будет волатильным (а особенно в том случае, если оптимизатор не имеет доступа к функции, принимающей указатель)

вне зависимости от волатильности переменной[/em]

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


Текущее время: 08:30. Часовой пояс GMT +3.