Тупик в реализации блокировки MCS
Оборудование: Ядро Darwin Версия 13.2.0: Четверг, 17 апреля 23:03:13 PDT 2014; root: xnu-2422.100.13 ~ 1 / RELEASE_X86_64 x86_64
atomics.hpp 1 #ifndef ATOMIC_UTILS_H 2 #define ATOMIC_UTILS_H 3 4 # включите 5 6 #define BARRIER() __asm__ volatile ( "":::"memory") 7 8 #define CPU_RELAX() __asm__ volatile( "пауза \n\t":::"память") 9 10 #define STORE_FENCE() __asm__ volatile("mfence"::: "memory"); 11 12 класс AtomicUtils 13 { 14 публичных: 15 16 /** 17 * проверить, равно ли значение в addr oldval, если это так, заменить его на newva l 18 * и вернуть старвал 19 */ 20 встроенных статических size_t compareAndExchange( volatile size_t* addr, size_t oldval, size_t newval) 21 { 22 size_t ret; 23 __asm__ volatile( "блокировка cmpxchgq %2, %1\n\t" 24:"=a"(ret), "+m"(*addr) 25: "r"(newval), "0"(oldval) 26: "память"); 27 возвращение в отставку; 28 } 29 30 /** 31 * атомно сохраняет x в addr и возвращает предыдущий 32 * хранится в адресе 33 */ 34 встроенных статических size_t loadAndStore( size_t x, volatile size_t* addr) 36 { 37 size_t ret; 38 __asm__ volatile( "блокировка xchgq %1, %0\n\t" 39: "+m"(*addr), "=r"(ret) 40: "1"(х)); 41 возвратный удар; 42 } 43 44 }; 45 46 # конец
mcs.hpp 1 #ifndef MCS_LOCK_H 2 #define MCS_LOCK_H 3 4 #include "atomics.hpp" 5 # включите 6 7 класс MCSLock 8 { 9 struct mcs_lock_t 10 { 11 mcs_lock_t(): следующий (0), заблокирован (ложь){} 12 struct mcs_lock_t* next; 13 бул заблокирован; 14 }; 15 16 публичных: 17 typedef struct mcs_lock_t mcs_lock; 18 19 личное: 20 mcs_lock** хвост; 21 статическое повышение:: thread_specific_ptr tls_node; 22 23 публичных: 24 MCSLock( mcs_lock** lock_tail): хвост ( lock_tail) 25 { 26 if( tls_node.get() == 0) 27 tls_node.reset( new mcs_lock()); 28 } 29 30 void lock() 31 { 32 mcs_lock* thread_node = tls_node.get(); 33 thread_node->next = 0; 34 thread_node->locked = true; 35 36 изменчивых mcs_lock* pred = reinterpret_cast( 37 AtomicUtils::loadAndStore( 38 reinterpret_cast( thread_node), 39 reinterpret_cast( tail) 40) 41); 42 if( pred!= 0) 43 { 44 пред-> следующий = * хвост; 45 46 STORE_FENCE(); 47 // БАРЬЕР (); // Требуется для предотвращения повторного упорядочения между prev->next = tail и thread_node->locked. ( WR Гарзард) 48 49 // Вращаем локальную переменную. Кто-нибудь разблокировать меня плз! 50 while( thread_node-> заблокирован) 51 CPU_RELAX(); 52 53 } 54 } 55 56 void unlock() 57 { 58 mcs_lock* thread_node = tls_node.get(); 59 if( thread_node->next == 0) 60 { 61 // Если false, то у нового потока есть запрос на блокировку. Теперь отпустите блокировку новой темы. 62 если ( 63 AtomicUtils::compareAndExchange( 64 reinterpret_cast( tail), 65 reinterpret_cast( thread_node), 66 0 67) == reinterpret_cast( thread_node) 68) 69 { 70 возвращение; 71 } 72 73 while( thread_node->next == 0) 74 CPU_RELAX(); 75 } 76 77 thread_node->next->locked = false; 78 } 79 }; 80 81 boost::thread_specific_ptr MCSLock::tls_node; 82 #endif
mcs_test.cpp
1 #include "mcs.hpp"
2 #include <iostream>
3 #include <pthread.h>
4 #include <vector>
5 #define NUM_THREADS 16
6 #define NUM_ITERATIONS 100
7
8 std::vector<int> elements;
9 MCSLock::mcs_lock *tail = 0;
10
11 void* thread_run( void* data )
12 {
13 MCSLock lock( &tail );
14 for( int i = 0; i < NUM_ITERATIONS; ++i )
15 {
16 lock.lock();
17 elements.push_back( i );
18 lock.unlock();
19 }
20
21 return 0;
22 }
23
24 int main()
25 {
26 pthread_t threads[ NUM_THREADS ];
27 elements.reserve( NUM_THREADS * NUM_ITERATIONS );
28
29 {
30 for( int i = 0; i < NUM_THREADS; ++i )
31 pthread_create( &threads[i], NULL, thread_run, NULL );
32
33 for( int i = 0; i < NUM_THREADS; ++i )
34 pthread_join( threads[i], NULL );
35
36 std::cout <<"\nExiting main thread: " << std::endl;
37 }
38 }
Приведенный выше код скомпилирован с использованием Clang
Проблема:
Я вижу, что 1 или 2 потока застряли в lock () в строке 50. За исключением основных потоков, потоков, которые застряли в lock (), другие живые потоки отсутствуют. Это означает, что когда другие потоки вызывают unlock (), они почему-то не устанавливают значение false = false для других переменных и завершаются.
Любые указатели по отладке этого, пожалуйста?
Застрял на этом много часов и никаких подсказок.
1 ответ
Разве у clang нет встроенных модулей для этих блоков inline-asm (например, __sync_val_compare_and_swap) в gcc? Зачем заново изобретать колесо?
Во-вторых, я бы действительно подумал о добавлении Clobber памяти в loadAndStore. Вы должны убедиться, что все записи, которые компилятор хранит в регистрах, сбрасываются в память перед выполнением xchgq. Точно так же это предотвратит gcc от оптимизации чтения памяти до xchgq. Либо было бы плохо.
В-третьих, я бы изучил вывод asm для ваших циклов while (thread_node->locked и thread_node->next). Поскольку эти переменные не являются изменчивыми, gcc может оптимизировать это, чтобы выполнить чтение только один раз.
Это может не решить вашу проблему, но я бы начал с этого.