Есть статическая переменая int x;
Есть метод в котором один оператор x++;
Запускаю 100 потоков которые вызывают данный метод получаю результат 100 или 99.
То что нужно использовать синхронизацию это понятно, хотелось бы понять почему именно только 2 результата получается 100 и 99
а не 98, 101 и т.п.
Здравствуйте, Аноним, Вы писали:
А>Есть статическая переменая int x; А>Есть метод в котором один оператор x++; А>Запускаю 100 потоков которые вызывают данный метод получаю результат 100 или 99.
А>То что нужно использовать синхронизацию это понятно, хотелось бы понять почему именно только 2 результата получается 100 и 99 А>а не 98, 101 и т.п.
101 быть не может, если ты вызываешь инкремент всего 100 раз. А так вероятность коллизии скорее всего маленькая. Пока запускаются новые потоки (долгая операция), другие уже отработали.
Здравствуйте, Аноним, Вы писали:
А>Есть статическая переменая int x; А>Есть метод в котором один оператор x++; А>Запускаю 100 потоков которые вызывают данный метод получаю результат 100 или 99.
А>То что нужно использовать синхронизацию это понятно, хотелось бы понять почему именно только 2 результата получается 100 и 99 А>а не 98, 101 и т.п.
100 получается понятно почему. Это в случае когда 100 потоков выполнили оператор x++ и не пересеклись во времени, не выполняли эту операцию одновременно.
99 — тоже понятно почему. Два потока без синхронизации значение x, оба инкрементировали и записали оба примерно одновременно.
По поводу 101- непонятно, откуда должно взяться 101, ведь потоков 100 и каждый инкрементирует лишь один раз. Вот если бы каждый прибавлял 2, а потом вычитал один — тогда 101 было бы возможно.
Для того что бы увидеть больший разбег от ожидаемого числа, предлагаю зациклить каждый поток до 10000, а потом сравнить результат в x с 1000000.
Re: многопоточность инкремент
От:
Аноним
Дата:
04.06.13 17:22
Оценка:
Здравствуйте, Аноним, Вы писали:
А>Есть статическая переменая int x; А>Есть метод в котором один оператор x++; А>Запускаю 100 потоков которые вызывают данный метод получаю результат 100 или 99.
А>То что нужно использовать синхронизацию это понятно, хотелось бы понять почему именно только 2 результата получается 100 и 99 А>а не 98, 101 и т.п.
Еще вопрос, если запускать в студии в Release конфигурации, то ситуация воспроизводится хорошо.
Если запустить просто exe файл release без студии , то не воспроизводится, даже если увеличить на порядок количество потоков.
Студия ставит синхронизацию не явно ?
Здравствуйте, Аноним, Вы писали:
А>То что нужно использовать синхронизацию это понятно, хотелось бы понять почему именно только 2 результата получается 100 и 99 А>а не 98, 101 и т.п.
Сколько ядер?
Re[2]: многопоточность инкремент
От:
Аноним
Дата:
04.06.13 17:30
Оценка:
Здравствуйте, 0x7be, Вы писали:
0>Здравствуйте, Аноним, Вы писали:
А>>То что нужно использовать синхронизацию это понятно, хотелось бы понять почему именно только 2 результата получается 100 и 99 А>>а не 98, 101 и т.п. 0>Сколько ядер?
Здравствуйте, Аноним, Вы писали:
0>>Сколько ядер? А>Core i5-2410M
Процессор двухъядерный.
Есть гипотеза, что связано с этим.
Интереса ради, попробуй найти 6-8 ядерную тачку и запусти тест на ней.
Re[4]: многопоточность инкремент
От:
Аноним
Дата:
04.06.13 17:53
Оценка:
Здравствуйте, 0x7be, Вы писали:
0>Здравствуйте, Аноним, Вы писали:
0>>>Сколько ядер? А>>Core i5-2410M 0>Процессор двухъядерный. 0>Есть гипотеза, что связано с этим. 0>Интереса ради, попробуй найти 6-8 ядерную тачку и запусти тест на ней.
А есть идея почему вот такой код, ( специально замудрил y и sin чтобы оптимизатору было сложнее и вероятность ошибок повысить, так по сути это тот же x++ );
в Release сборке работает без сбоев, всегда выдает ровно 1000 если запускать без студии.
class Program
{
static int x;
static int y;
static void Main(string[] args)
{
while(true)
{
var t = new List<Thread>();
for (int i = 0; i < 1000; i++)
{
var th = new Thread(new ParameterizedThreadStart(o => { y = x + (int)Math.Sin(Math.PI/2); x = 1 + y; x = x - 1; }));
t.Add(th);
}
for (int i = 0; i < t.Count; i++)
t[i].Start();
for (int i = 0; i < t.Count; i++)
t[i].Join();
if (x != 1000)
Console.Write(x + " ");
x = 0;
}
}
}
При этом если заглянуть в ildasm для релизного exe файла то там для расчета генерится такой код, явно не оптимизированных/не атомарный и без синхронизаций
Здравствуйте, Аноним, Вы писали: А>А есть идея почему вот такой код, ( специально замудрил y и sin чтобы оптимизатору было сложнее и вероятность ошибок повысить, так по сути это тот же x++ ); А>в Release сборке работает без сбоев, всегда выдает ровно 1000 если запускать без студии.
Потому что в Release нет лишнего кода в математике, и поток номер N успевает закончится до того, как отработает thread.Start для потока номер N+1.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[6]: многопоточность инкремент
От:
Аноним
Дата:
05.06.13 16:23
Оценка:
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, Аноним, Вы писали: А>>А есть идея почему вот такой код, ( специально замудрил y и sin чтобы оптимизатору было сложнее и вероятность ошибок повысить, так по сути это тот же x++ ); А>>в Release сборке работает без сбоев, всегда выдает ровно 1000 если запускать без студии. S>Потому что в Release нет лишнего кода в математике, и поток номер N успевает закончится до того, как отработает thread.Start для потока номер N+1.
Вот добавил ManualResetEvent и сигнализирую начало расчетов когда для всех потоков вызовется Start , %ошибок сразу существенно возрос, причем стали попадаться и 97 , 98.
Но еще появился вопрос, если поставить между Start() и e.Set() Thread.Sleep(1000) то ошибки резко пропадают, с чем это связанно ?
Потоки засыпают и потом не одновременно просыпаются ?
class Program
{
static int x;
static ManualResetEvent e = new ManualResetEvent(true);
static void Job(object o)
{
e.WaitOne();
x++;
}
static void Main(string[] args)
{
while(true)
{
var t = new List<Thread>();
for (int i = 0; i < 100; i++)
{
var th = new Thread(new ParameterizedThreadStart(Job));
t.Add(th);
}
e.Reset();
for (int i = 0; i < t.Count; i++)
t[i].Start();
//Thread.Sleep(1000);
e.Set();
for (int i = t.Count-1; i >=0; i--)
t[i].Join();
Console.Write(x + " ");
x = 0;
}
}
}
Несмотря на простату этого statement'а для процессора он состоит из трех шагов:
1) считать значение переменной x
2) прибавить единицу к значению полученному на шаге 1
3) присвоить переменной x значение полученное на шаге 2
Есть вероятность что два (или даже более (что менее вероятно)) потока одновременно считают одно и тоже текущее значение переменной x.
Далее на шаге 2 получат одно тоже инкрементированное значение, и соответственно на шаге 3 запишут одно и тоже значение в переменную x.
Получится что 1 (или даже более (что менее вероятно)) поток сработает в холостую.
Теоретически таких коллизий может случиться несколько.
Обозначим за n число потоков. У тебя n = 100.
Итоговое значение x может быть только меньше либо равно n.
Обозначим разницу между n и итоговым значением x за d.
Чем больше n, тем больше может быть d.
Так как чем больше потоков, тем больше коллизий может произойти.
А сколько конкретно произойдет коллизий вопрос случая.
Естественно большой количество коллизий менее вероятно, чем малое.
Могу предположить что влияние здесь оказывает компиляция под Release с оптимизациями. В этом случае код x = x + 1 становится более оптимизированным, то есть занимает меньшее число инструкций процессора и поэтому вероятность коллизий меньше, чем с не оптимизированным кодом. Иными словами операция x = x + 1 становится более атомарной, но полную атомарность может гарантировать только Interlocked.Increment или другие примитивы синхронизации (например Monitor).
Еще могу предположить что увеличение количества ядер CPU увеличит вероятность коллизий.
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания http://rsdn.ru/Info/rules.xml