#multithreading #locking #x86-64 #atomic #inline-assembly
#многопоточность #блокировка #x86-64 #атомарный #встроенная сборка
Вопрос:
Аппаратное обеспечение: Версия ядра 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 #определить ATOMIC_UTILS_H 3 4 #включить 5 6 #определить БАРЬЕР() __asm__ volatile ( "": : :"память") 7 8 #определить CPU_RELAX() __asm__ volatile( "пауза n t": : :"память") 9 10 #определить STORE_FENCE() __asm__ volatile("защита" ::: "память"); 11 12 классов AtomicUtils 13 { 14 общедоступных: 15 16 /** 17 * проверьте, равно ли значение в addr старому значению, если да, замените его на 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" (новое значение), "0" (старое значение) 26 : "память"); 27 возвращает ret; 28 } 29 30 /** 31 * Атомарно сохраняет x в addr и возвращает предыдущее 32 * сохранено в addr 33 */ 34 встроенный статический размер_t loadAndStore ( размер_t x, изменяемый размер_t* addr) 36 { 37 size_t ret; 38 __asm__ volatile( "заблокировать xchgq %1, %0 n t" 39: " m"(*addr), "=r"(ret) 40: "1"(x) ); 41 возврат ret; 42 } 43 44 }; 45 46 #endif
mcs.hpp 1 #ifndef MCS_LOCK_H 2 #определить MCS_LOCK_H 3 4 #включить "atomics.hpp" 5 #включить 6 7 класс MCSLock 8 { 9 структура mcs_lock_t 10 { 11 mcs_lock_t(): следующий (0), заблокирован (false){} 12 struct mcs_lock_t* далее; 13 bool заблокирован; 14 }; 15 16 общедоступных: 17 структура typedef 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( новый mcs_lock()); 28 } 29 30 аннулирует блокировку() 31 { 32 mcs_lock* thread_node = tls_node.get(); 33_нод потока-> следующий = 0; 34_нод потока->заблокирован = true; 35 36 volatile mcs_lock* pred = reinterpret_cast( 37 AtomicUtils::loadAndStore( 38_интерпретация (thread_node), 39 reinterpret_cast(хвост) 40 ) 41 ); 42 if( pred != 0) 43 { 44 pred-> next = *хвост; 45 46 STORE_FENCE(); 47 //БАРЬЕР(); // Требуется для предотвращения изменения порядка между prev-> next = tail и thread_node->заблокирован. ( Р. Р. Харзард) 48 49 // Запуск локальной переменной. Кто-нибудь, разблокируйте меня, пожалуйста!! 50 в то время как ( thread_node->заблокирован) 51 CPU_RELAX(); 52 53 } 54 } 55 56 аннулирование разблокировки () 57 { 58 mcs_lock* thread_node = tls_node.get(); 59 если( thread_node->next == 0) 60 { 61 // Если false, то у нового потока есть запрос на блокировку. Теперь снимите блокировку для нового потока 62 если( 63 AtomicUtils::compareAndExchange( 64 reinterpret_cast(хвост), 65_интерпретация (thread_node), 66 0 67 ) == reinterpret_cast( thread_node ) 68) 69 { 70 возврат; 71 } 72 73 в то время как( thread_node->next == 0) 74 CPU_RELAX(); 75 } 76 77_нод потока-> следующий-> заблокирован = 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( amp;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( amp;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(), они каким-то образом не устанавливают значение locked = false для других переменных и не завершают работу.
Какие-либо указания по отладке этого, пожалуйста?
Застрял на этом много часов и никаких подсказок.
Комментарии:
1. К вашему сведению, перенос ваших собственных блокировок с помощью встроенного asm обычно бессмысленен. Я мог бы увидеть преимущество написания вашей собственной очереди без блокировки, но использование блокировок библиотеки вокруг обычной очереди имело бы больше смысла. Даже для очереди без блокировки используйте C 11
std::atomic
вместо ручного развертывания собственных примитивов поверх изменяемых объектов.2. @PeterCordes: Я думаю, стоит рассмотреть это в контексте. Об этом спрашивали 5-6 лет назад. Большинство стабильных версий дистрибутивов не использовали GCC, который поддерживал атомарные примитивы C 11. Когда-то вам приходилось делать собственный откат.
3. @MichaelPetch: Я видел дату в вопросе, через 3 года после C 11. Да, справедливо отметить, что std:: atomic был относительно новым на тот момент, но в то время это был определенно правильный выбор, даже если это означало установку обновленного набора инструментов. Что еще более важно, это определенно правильный выбор сейчас для любых нынешних / будущих читателей вопроса.
Ответ №1:
Разве в clang нет встроенных модулей для этих встроенных блоков asm (например, __sync_val_compare_и_swap в gcc)? Зачем заново изобретать колесо?
Во-вторых, я бы действительно подумал о добавлении переполнения памяти в loadAndStore. Вам нужно убедиться, что все записи, которые компилятор хранит в регистрах, сбрасываются в память перед выполнением xchgq. Аналогично, это не позволит gcc оптимизировать операции чтения в памяти до xchgq. И то, и другое было бы плохо.
В-третьих, я бы проверил вывод asm для ваших циклов while (thread_node->заблокирован и thread_node-> следующий). Поскольку эти переменные не являются изменчивыми, gcc может оптимизировать это, чтобы выполнить чтение только один раз.
Возможно, это не решит вашу проблему, но именно с этого я бы начал.