тестирование «кеш не сброшен в основную память» для моей реализации кольцевого буфера

обн чтобы отразить последнее сделанное предложение curWriteNum изменчивый, переставленный pool.Commit(); с std::cout

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

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

#include <stdint.h>
#include <iostream>
#include <boost/thread.hpp>

#include <chrono>
#include <thread>

template<class T> class ArrayPool
{
public:
ArrayPool() {
};

~ArrayPool(void) {
};

bool IsEmpty() {
return curReadNum == curWriteNum;
}

T* TryGet()
{
if (curReadNum == curWriteNum)
{
return NULL;
}
T* result = &storage[curReadNum & MASK];
++curReadNum;
return result;
}

T* Obtain() {
return &storage[curWriteNum & MASK];
}

void Commit()
{
++curWriteNum;
if (curWriteNum - curReadNum > length)
{
std::cout <<
"ArrayPool curWriteNum - curReadNum > length! " <<
curWriteNum << " - " << curReadNum << " > " << length << std::endl;
}
}

private:
static const uint32_t length = 65536;
static const uint32_t MASK = length - 1;
T storage[length];
volatile uint32_t curWriteNum;
uint32_t curReadNum;
};

struct myStruct {
int value;
};

ArrayPool<myStruct> pool;

void ReadThread() {
myStruct* entry;
while(true) {
while ((entry = pool.TryGet()) != NULL) {
std::cout << entry->value << std::endl;
}
}
}

void WriteThread(int id) {
std::chrono::milliseconds dura(1000 * id);
std::this_thread::sleep_for(dura);

myStruct* storage = pool.Obtain();
storage->value = id;
pool.Commit();
std::cout << "Commited value! " << id << std::endl;
}int main( void )
{
boost::thread readThread = boost::thread(&ReadThread);
boost::thread writeThread;
for (int i = 0; i < 100; i++) {
writeThread = boost::thread(&WriteThread, i);
}
writeThread.join();
return 0;
}

Я пытался запустить эту программу на сервере 2 * Xeon E5, и все в порядке, каждое значение было поймано:

...
Commited value! 19
19
Commited value! 20
20
Commited value! 21
21
Commited value! 22
22
Commited value! 23
23
Commited value! 24
24
Commited value! 25
25
Commited value! 26
26
....

Также в Process Explorer я вижу, как количество потоков уменьшается с ~ 101 до 1.
Означает ли это, что мой класс ArrayPool в порядке, и на современных процессорах Intel невозможно столкнуться с подобными проблемами? Если возможно воспроизвести проблему «кеш-памяти», то как это сделать?

2

Решение

Прежде всего, нет, эта программа не является безопасной. Это может быть правдой, что вы не можете воспроизвести проблемы с упорядочением кэша в вашем конкретном сочетании компилятора и архитектуры. Особо отметим, что это касается не только вашего кеша процессора. Теоретически вашему компилятору разрешено менять операции присваивания. Итак, что вы можете сделать, чтобы увеличить свой риск?

  • Попробуйте разные компиляторы на разных (чаще всего высоких) уровнях оптимизации. Например, если я скомпилирую с x86_64-linux-gnu-g++-4.8 -O3Читатель пропускает все значения. Скорее всего, компилятор TryGet и замечает, что тело цикла не влияет на условие. Поэтому он может кешировать результат условия в значение регистра. Чтобы избежать такого поведения, вам нужно пометить одну из переменных в условии как volatile,
  • Попробуйте разные архитектуры процессоров (для разного поведения кэша).
  • Между вашими Commit и фактическое значение записи, происходит системный вызов. Это занимает значительное количество времени. Поменяйте местами эти операции.

Даже если вы хотите обходиться без блокировки, вам понадобятся как минимум барьеры для чтения и записи. Посмотри на эта статья LWN чтобы понять сложность и изучить библиотеку, которая поможет вам писать алгоритмы без блокировок.

1

Другие решения

Других решений пока нет …

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector