Могу ли я использовать часть моих данных в качестве блокировки?

У меня большой массив (3e9 элементов) данных, и я обновляю его значение в нескольких потоках. Я только что узнал, что есть условия гонки.

Я думаю, что нет необходимости блокировать всю функцию, так как элементы не зависят друг от друга, обновление на data[1] а также data[234] может быть безопасно сделано в то же время.

Я также нашел самый значительный бит каждого элемента в data[] никогда не будет использоваться. Безопасно ли реализовывать атомарную встроенную блокировку GCC для этого бита?

Мой код выглядит следующим образом, но, похоже, он заходит в тупик.

const unsigned short LOCK_MASK = 1<<15;
unsigned short * lock = &(data[position]);
unsigned short oldLock, newLock;

//lock
do {
oldLock = *lock;
newLock = oldLock ^ LOCK_MASK;
} while ((oldLock & LOCK_MASK) || !__sync_bool_compare_and_swap(lock, oldLock, newLock));

//update data[position] here
...
...
...

//unlock
*lock ^= LOCK_MASK;

Я тоже читал этот пост (Легкие спин-блокировки, созданные из атомных операций GCC?) и добавил volatile на моем data

РЕДАКТИРОВАТЬ В моем дизайне 0 означает разблокирован, а 1 означает заблокирован

2

Решение

Ваш код содержит ряд данных гонок, в том числе oldLock = *lock и разблокировать бит *lock ^= LOCK_MASK,
который не может синхронизировать ваши обновления с другими ядрами из-за отсутствия барьера выпуска.

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

Безопасно ли реализовывать атомарную встроенную блокировку GCC для этого бита?

Несколько битов требуются, если вы хотите выразить отдельные состояния для чтения и записи (разблокирован, заблокирован на чтение x N, заблокирован на запись).
Один бит ограничивает блокировку до 2 состояний, запертый а также разблокирована, который, основываясь на вашем коде, может быть реализован с помощью:

const unsigned short LOCK_MASK = 1<<15;

void lock_array_segment(int position)
{
unsigned short *lock = &data[position]; // global array
unsigned short oldLock, newLock;

do {
oldLock = __atomic_load_n (lock, __ATOMIC_RELAXED);
newLock = oldLock | LOCK_MASK; // set bit

} while ((oldLock & LOCK_MASK) || !__sync_bool_compare_and_swap(lock, oldLock, newLock));
}void unlock_array_segment(int position)
{
unsigned short *lock = &data[position]; // global array
unsigned short oldLock, newLock;

oldLock = __atomic_load_n (lock, __ATOMIC_RELAXED);
newLock = oldLock & ~LOCK_MASK; // clear bit

__atomic_store_n (lock, newLock, __ATOMIC_RELEASE);
}

Документация для __sync_bool_compare_and_swap говорит В большинстве случаев эти встроенные функции считаются полным барьером.. Здесь вам нужен барьер для приобретения, так что он должен быть покрыт.

Поскольку ваш подход основан на спин-блокировке, он не работает, если вы хотите удерживать блокировку чтения в течение более длительного времени. В этом случае рассмотрим более простой подход с отдельным мьютексом для каждого сегмента в массиве данных, который необходимо заблокировать.
Если вы хотите предоставить доступ нескольким читателям, рассмотрите возможность использования std::shared_mutex (C ++ 17) или boost::shared_mutex,

1

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

Вы должны рассмотреть более стандартные способы блокировки (в C ++ 11 или лучше).

Возможно, начните с чтения некоторых Учебник Pthread (по крайней мере, для концепций, объясненных там).

Прочитать о атомные операции а также поддержка потоков в C ++.

Вы можете эвристически рассмотреть возможность наличия мьютекса для каждого сегмента последовательных 1024 (или какой-либо другой степени двух) элементов.

Вы могли бы рассмотреть производитель-потребитель подход.

0

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