К списку форумов К списку тем
Регистрация    Правила    Главная форума    Поиск   
Имя: Пароль:
Рекомендовать в новости

ОФФ/4: INC(I), что сложного? Почему так сложно реализовали?

Гость
0 - 16.02.2013 - 00:32
Delphi (думаю любой версии)
Имеем массу одновременных потоков, к примеру 30 шт.
В каждом свой цикл в 1000 раз.
В кажтом цикле есть inc(i) - i это глобальная переменная.

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

ёпрст...



Гость
1 - 16.02.2013 - 08:48
0-ELEA > В замечательном языке С++ для этого есть ключевое слово volatile, а в Windows API семейство функций InterlockedXXX.

ЗЫ: Паскаль ацтой, С++ 2011 в народ! :))
Гость
2 - 16.02.2013 - 09:14
Может туда прикрутить какой-нибудь EnterCriticalSession ? :-)
А то пока один поток делает...
Push ebp
mov ebp,esp
enter 15,2
(или как там у них)
...другой уже досчитал и сохраняет результат.
Гость
3 - 16.02.2013 - 10:18
Все правильно. Потоки выполняются одновременно. Если в каждом потоке две команды A и B, то возможно, что порядок выполнения для двух потоков будет таков:

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

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

Что же касается любимого мною Delphi, то многопоточное программирование никогда не было его сильной стороной, ИМХО. Тот же TThread, главным образом, нужен для того, что вынести в поток тяжелую операцию, не "повесив" при этом интерфейс.
Гость
4 - 16.02.2013 - 10:24
ИМХО подход неверный. Если будете использовать блокирующие функции будут ожидания в потоках. Попробуйте реализовать через очередь.
Гость
5 - 16.02.2013 - 12:41
ага сделать отдельный поток - инкрементор с очередью входящих сообщений в которую будут складываться нотариально заверенные запросы на увеличение

в то время как в нормальных языках давно есть AtomicInteger и подобные
Гость
6 - 16.02.2013 - 14:11
Цитата:
Сообщение от NTFS_ Посмотреть сообщение
Потому любая операция, которая занимает более одной команды ассемблера, должна оформляться специальным образом.
Я так понял, топикстартер и ждёт, что это будет одна ассемблерная команда inc.
Цитата:
Сообщение от ELEA Посмотреть сообщение
не просто инкрементирует, а чтото кудато запооминает, там инкрементирует и назад кладет
Всё верно, это оптимизация - компилятор видит, что переменная нигде не используется, её выгоднее инкрементировать в регистре, чем в ячейке памяти, того, что переменная может быть задействована в другом потоке, компилятор не предполагает. Попробуй, ради интереса, инкрементировать переменную в асм вставке, тогда оптимизатор не вмешается.
Гость
7 - 16.02.2013 - 15:46
to0 Так с потоками не работают ;) Как ты написал так вот и работает, т.е. если нет синхронизации доступа к глобальным данным то и нечего удивляться такому результату.

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

to1 volatile в общем случае ничего не гарантирует, а лишь говорит компилятору что значение переменной может быть изменено из вне и при работе с этой переменной нельзя использовать оптимизации. И не более, и еще то как это должно быть сделано вроде не прописано в стандарте и по этому поведение при volatile еще будет зависеть от того как это сделает компилятор.
Гость
8 - 16.02.2013 - 17:09
1-Максыч > обязательно ктонить скажет такое. Ну не могут не сказать: твой выбор гавно, на дворе другое время и номальные люди выбирают другое.
Гость
9 - 16.02.2013 - 19:10
to8 Ну ты как не местный ;) и даже не из нашей страны.
Иностранный агент что ли. У нас на форумах принято вопрошающему объяснять какой он (тут сам придумай ругательство), а не отвечать на его вопрос.

А delphi к сожалению сдал позиции, не смотря на потуги эмбаркодеро.
Гость
10 - 16.02.2013 - 19:33
7-TVV1 > to1 volatile в общем случае ничего не гарантирует, а лишь говорит компилятору что значение переменной может быть изменено из вне и при работе с этой переменной нельзя использовать оптимизации - мбугого, еще перлов в студию! :)) Оно как раз и гарантирует то, что "при работе с этой переменной нельзя использовать оптимизации", а это и есть одна из проблем у автора.
8-ELEA >
Чего тебе не так? Я тебе лично что-то сказал? Я тебе даже функции назвал, которыми пользоваться надо, а ты еще и бочку на меня катишь. Странный ты человек. :(
Гость
11 - 16.02.2013 - 22:35
6-Mikle Quits > в XE2 asm убрали... :(

10-Максыч > Да дело в том, что вопрос небыл как исправить, или как решить. Я и сам умею решать такие проблемки... И даже небыл вопрос - почему делфи мертв или както так.
Мне интересно, почему столь простую функцию/процедуру реализовали не столь просто.
а в Си - ++i так же себя ведет?
Гость
12 - 16.02.2013 - 22:36
6-Mikle Quits > " что переменная нигде не используется, её выгоднее инкрементировать в регистре, чем в ячейке памяти" - спасибо за мысль. Похоже, что истина гдето именно в этом.
Гость
13 - 17.02.2013 - 01:53
>а в Си - ++i так же себя ведет?
А почему бы и нет :)
Тут ведь несколько действий: считать значение, увеличить на единицу, записать значение. И по скольку эти действа происходят в параллельно выполняющихся потоках, то отсюда все фокусы.
Вот, например, пусть есть два потока и глобальная переменная i=0.
Первый поток читает значение i, увеличивает его на единицу, но не успевает это дело записать обратно и его квант заканчивается. Стартует второй поток читает значение i видит что оно равно нулю, увеличивает его на 1 и записывает результат обратно, т.е. i=1.
Затем квант получает второй поток и записывает в i свою 1. И того в i вместо 2 будет 1.
Гость
14 - 17.02.2013 - 06:31
11-ELEA > В С++ некоторые отважные производители компиляторов даже взяли на себя ответственность и за разруливание атомарных операций с целыми при использовании volatile (вот честно - я бы завязывался на компиляторе иногда, и не тупил про "кроссплатформенность", когда какой-то строптивый олень не захотел реализовать параметр once для директивы #pragma . Потому что хорошие вещи становятся стандартом обычно де-факто). Да, и еще я думаю, что производители хороших компиляторов С++ получше смогут разрулить такую ситуацию. А ответ на твой вопрос "почему" был где-то в моем посте №1, но ты почему-то обиделсо.

Кстати, а как ты решил столь страшную проблему?
12-ELEA > Ахринеть, а Сишники об этом с рождения знают. :)13-TVV1 > Откуда поток берет, и куда не успевает записать?
Гость
15 - 23.02.2013 - 21:13
1-Максыч > Это заблуждение.
volatile в C++ гарантирует volatile_write, но не гарантирует volatile_read (так же как и в C#), а потому возможны гонки первого рода, к тому же инкремент - не атомарная операция (собственно поэтому от синронизации никуда не уйти, InterlockedXXX - тоже функции синхронизации).

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

Отредактировано XentaAbsenta; 23.02.2013 в 21:25. Причина: дополнение, исправление грамматики
Гость
16 - 23.02.2013 - 21:15
насколько мне известно, volatile правильно работает только в JVM 1.5
Гость
17 - 23.02.2013 - 21:17
да, маленькое уточнение: разговор идёт об x86
Гость
18 - 02.03.2013 - 01:46
15-Философ > я только за С++ говорил, если Вы (ты) не заметили.
Ну и маленькое уточнение - "при использовании InterlockedXXX помечать переменную как volatile не обязательно."
http://msdn.microsoft.com/ru-RU/libr...(v=vs.85).aspx
А "ОНИ" думают по-другому. Ай-яй-яй.
Гость
19 - 03.03.2013 - 23:23
если вспомнить во что компилируется код с волатильными переменными, то логично предполагать, что указатель в любом случае будет волатильным (а особенно в том случае, если оптимизатор не имеет доступа к функции, принимающей указатель)

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

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

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

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

int _tmain(int argc, _TCHAR* argv[])
**
LONG ii = 0;
InterlockedIncrement(*ii);
return 0;
**
Гость
20 - 06.03.2013 - 20:57
На чем компилируется, забыл уточнить?
Гость
21 - 06.03.2013 - 21:06
20-Максыч > Судя по _tmain, студия.
Вот только какая, О_о?? Версии 1.0 alpha? Как она съела разыменование переменной типа LONG, ужаснах..
Про то, что она без приведения типа не скомпилировала бы и неявное приведение nonvolatile к volatile-указателю - я уже молчу.
Гость
22 - 06.03.2013 - 21:11
+21, точнее, в данном случае может бы и съела. Но это уже "умный" компилятор, а вообще, рассуждать вот так, и, что еще хуже - пользоваться таким в жизни:

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

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


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


К списку вопросов






Copyright ©, Все права защищены