В последнее время, когда я смотрю в то, как поток локального хранения осуществляются в glibc, я нашел следующий код, который реализует API pthread_key_create()
Почему доступ к порядковым номерам ключей pthread не синхронизирован в реализации NPTL glibc?
int
__pthread_key_create (key, destr)
pthread_key_t *key;
void (*destr) (void *);
{
/* Find a slot in __pthread_kyes which is unused. */
for (size_t cnt = 0; cnt < PTHREAD_KEYS_MAX; ++cnt)
{
uintptr_t seq = __pthread_keys[cnt].seq;
if (KEY_UNUSED (seq) && KEY_USABLE (seq)
/* We found an unused slot. Try to allocate it. */
&& ! atomic_compare_and_exchange_bool_acq (&__pthread_keys[cnt].seq,
seq + 1, seq))
{
/* Remember the destructor. */
__pthread_keys[cnt].destr = destr;
/* Return the key to the caller. */
*key = cnt;
/* The call succeeded. */
return 0;
}
}
return EAGAIN;
}
__pthread_keys
является глобальным массивом, доступом всех потоков. Я не понимаю, почему чтение его члена seq
не синхронизирован как в следующем примере:
uintptr_t seq = __pthread_keys[cnt].seq;
хотя syncrhonized когда изменения позднее.
FYI, __pthread_keys
представляет собой массив типа struct pthread_key_struct
, которая определяется следующим образом:
/* Thread-local data handling. */
struct pthread_key_struct
{
/* Sequence numbers. Even numbers indicated vacant entries. Note
that zero is even. We use uintptr_t to not require padding on
32- and 64-bit machines. On 64-bit machines it helps to avoid
wrapping, too. */
uintptr_t seq;
/* Destructor for the data. */
void (*destr) (void *);
};
Заранее спасибо.
Проблема состоит в том, что чтение 'seq' может привести к промежуточному значению, поскольку тип' seint' 'uintptr_t' не гарантирует, что он читается ** атомарно ** (т.е. в течение одного цикла команды) , – spockwang
Если 'seq' является непоследовательным, поток будет продолжать следующий элемент, потому что сбой в атомной операции CAS завершится неудачно. Чтение находит слот-кандидат (который в конечном итоге не может быть использован), а CAS обеспечивает гарантию синхронизации. – jspcal
Я нашел 'pthread_key_delete()' также использую этот трюк. Он полагается на CAS, предоставляя гарантию синхронизации, чтобы убедиться, что он не удалит недействительный ключ. Но он может забыть удалить действительный ключ, неверно указав его как недействительный ключ. – spockwang