(если это важно), N тредов и общий стейт типа int. Вопросы:
1. Можно ли как-то "засечь" ("протестировать") torn read / torn write для int? Насколько я понимаю - на x86 чтение/запись слова всегда атомарна, если память выровнена
2. Можно ли как-то заалайнить общий стейт так, чтобы чтение/запись слова перестало быть атомарным?
3. Как вообще задизайнить тест на torn read / torn write? Пока в мыслях следующий алгоритм: цикл на ~1кк итераций, внутри стартуем два треда, один пишет в общий стейт "+1", второй - "-1", джойним треды, проверяем abs(state) == 1. Будет ли такой алгоритм вообще работать?
2. #pragma pack(push, 1) struct { char botva; int state; } state; #pragma pack(pop) 3. Звучит разумно, надо тестить.
стоит иметь в виду, что простой тип int при модификации из нескольких потоков не будет ничего знать о потоках и как следствие может не трогать память совсем. Это можно решить с помощью volatile, но от ub на уровне языка это не спасает. Возможно, на x86 это работать и начнет (volatile), но я не уверен. А вот просто int без volatile я вообще не уверен
2. Спасибо, так и сделал 3. К сожалению на 1кк итераций не удалось ничего словить (ни с int, ни с volatile int). Вот код теста: #include <iostream> #include <thread> #include <cassert> //typedef volatile int value_t; typedef int value_t; #pragma pack(push, 1) struct Packed { char padding; value_t data; }; #pragma pack(pop) int main() { Packed p; value_t* value_ptr = &p.data; int nIterations = 1000000; for (int i = 0; i < nIterations; i++) { std::thread t1 ([&]() { *value_ptr = 1; }); std::thread t2 ([&]() { *value_ptr = -1; }); t1.join(); t2.join(); assert(std::abs(*value_ptr) == 1); } return 0; }
а откуда взялось утверждение в п.1, про потерю атомарность у мува из-за невыровненного адреса?
Серьезных пруфов предоставить пока не могу. Сам читаю статьи по атомарности/барьерам в блоге Preshing on Programming, в одной из статей он пишет следующее: https://preshing.com/20130618/atomic-vs-non-atomic-operations/ Non-Atomic CPU Instructions ... As another example, it’s well-known that on x86, a 32-bit mov instruction is atomic if the memory operand is naturally aligned, but non-atomic otherwise. In other words, atomicity is only guaranteed when the 32-bit integer is located at an address which is an exact multiple of 4.
что-то сомнительно. Вот если пересечь границу строки кэша, тогда скорее всего можно сломать.
Чтоб иметь возможность словить проблему потоки должны стартануть почти в один момент. Я бы добавил ожидание на condition variable и сделал бы notify_all
8.1.1 Guaranteed Atomic Operations The Intel486 processor (and newer processors since) guarantees that the following basic memory operations will always be carried out atomically: • Reading or writing a byte • Reading or writing a word aligned on a 16-bit boundary • Reading or writing a doubleword aligned on a 32-bit boundary The Pentium processor (and newer processors since) guarantees that the following additional memory operations will always be carried out atomically: • Reading or writing a quadword aligned on a 64-bit boundary • 16-bit accesses to uncached memory locations that fit within a 32-bit data bus The P6 family processors (and newer processors since) guarantee that the following additional memory operation will always be carried out atomically: • Unaligned 16-, 32-, and 64-bit accesses to cached memory that fit within a cache line Accesses to cacheable memory that are split across cache lines and page boundaries are not guaranteed to be atomic by the Intel Core 2 Duo, Intel® Atom™, Intel Core Duo, Pentium M, Pentium 4, Intel Xeon, P6 family, Pentium, and Intel486 processors. The Intel Core 2 Duo, Intel Atom, Intel Core Duo, Pentium M, Pentium 4, Intel Xeon, and P6 family processors provide bus control signals that permit external memory subsystems to make split accesses atomic; however, nonaligned data accesses will seriously impact the performance of the processor and should be avoided.
Скажем так - выровненная операция записи приедет в память атомарно (в том смысле что если 2 ядра туда пишут, не будет такого что часть бит от одного а часть от другого). Доедет копия от кого-то одного. Это гарантирует протокол когерентности кешей. И это на большинстве архитектур.
Обсуждают сегодня