Мьютекс пример / учебник?
Я новичок в многопоточности, и пытался понять, как работают мьютексы. Сделал много гуглить и Я нашел неплохой учебник, но это все еще оставляло некоторые сомнения в том, как это работает, потому что я создал свою собственную программу, в которой блокировка не работала.
один абсолютно неинтуитивный синтаксис мьютекса pthread_mutex_lock( &mutex1 );, где похоже, что мьютекс блокируется, когда то, что я действительно хочу заблокировать, - это какая-то другая переменная. Означает ли этот синтаксис, что блокировка мьютекса блокирует a область кода до тех пор, пока мьютекс не будет разблокирован? Тогда как потоки знают, что область заблокирована? [обновление: потоки знают, что область заблокирована, поПамять Ограждения ]. И разве такое явление не должно называться критической секцией? [UPDATE: объекты критического раздела доступны только в Windows, где объекты быстрее мьютексов и видны только потоку, который его реализует. В противном случае критический раздел просто относится к области кода защищен мьютексом]
короче, не могли бы вы помочь с простой мьютекс программа и самый простой из возможных объяснение по логике, как это работает? Я уверен, что это поможет много других новичков.
8 ответов:
вот моя скромная попытка объяснить эту концепцию новичкам по всему миру: (a цветная версия в моем блоге тоже)
многие люди бегут в одинокую телефонную будку (без мобильных телефонов), чтобы поговорить со своими близкими. Первым, кто схватится за ручку двери будки, будет тот, кому разрешено пользоваться телефоном. Он должен продолжать держаться за ручку двери до тех пор, пока он использует телефон, иначе кто-то другой будет хвататься за ручку двери. обработайте, выбросьте его и поговорите с его женой :) нет никакой системы очереди как таковой. Когда человек заканчивает свой звонок, выходит из будки и оставляет ручку двери, следующий человек, который возьмет ручку двери, сможет использовать телефон.
A thread: каждый человек
Элемент мьютекс это: дверная ручка
Элемент замок - это : рука человека
Элемент ресурс есть : телефонлюбой поток, который должен выполнить некоторые строки кода, которые не должны быть изменены другими потоками одновременно (используя телефон, чтобы поговорить с женой), должен сначала получить замок на мьютексе (сжимая ручку двери кабины). Только тогда поток сможет запустить эти строки кода (сделать телефонный звонок).
как только поток выполнил этот код, он должен освободить блокировку на мьютексе, чтобы другой поток мог получить блокировку на мьютексе (другое люди, имеющие доступ к телефонной будке).
[концепция наличия мьютекса немного абсурдна при рассмотрении реального эксклюзивного доступа, но в мире программирования, я думаю, не было другого способа позволить другим потокам "видеть", что поток уже выполнял некоторые строки кода. Существуют понятия рекурсивные мьютексы и т. д., Но этот пример был только для того, чтобы показать вам основную концепцию. Надеюсь, этот пример дает вам четкое представление о концепции.]
с потоком C++11:
#include <iostream> #include <thread> #include <mutex> std::mutex m;//you can use std::lock_guard if you want to be exception safe int i = 0; void makeACallFromPhoneBooth() { m.lock();//man gets a hold of the phone booth door and locks it. The other men wait outside //man happily talks to his wife from now.... std::cout << i << " Hello Wife" << std::endl; i++;//no other thread can access variable i until m.unlock() is called //...until now, with no interruption from other men m.unlock();//man lets go of the door handle and unlocks the door } int main() { //This is the main crowd of people uninterested in making a phone call //man1 leaves the crowd to go to the phone booth std::thread man1(makeACallFromPhoneBooth); //Although man2 appears to start second, there's a good chance he might //reach the phone booth before man1 std::thread man2(makeACallFromPhoneBooth); //And hey, man3 also joined the race to the booth std::thread man3(makeACallFromPhoneBooth); man1.join();//man1 finished his phone call and joins the crowd man2.join();//man2 finished his phone call and joins the crowd man3.join();//man3 finished his phone call and joins the crowd return 0; }скомпилировать и запустить с помощью
g++ -std=c++0x -pthread -o thread thread.cpp;./threadвместо явного использования
lockиunlock, вы можете использовать скобки как показано здесь, если вы используете блокировку с областью действия для преимущества он дает. Однако ограниченные замки имеют небольшие накладные расходы на производительность.С TBB: Вам понадобится TBB для запуска программы ниже, но намерение проводка кода TBB заключается в том, что вы понимаете последовательность блокировки и разблокировки, просто глядя на простой код (возможно, показывали блокировку с областью действия, не используя acquire и release - , который также является исключение сейф -, но это яснее).
#include <iostream> #include "/tbb/mutex.h" #include "/tbb/tbb_thread.h" using namespace tbb; typedef mutex myMutex; static myMutex sm; int i = 0; void someFunction() { //Note: Since a scoped lock is used below, you should know that you //can specify a scope for the mutex using curly brackets, instead of //using lock.acquire() and lock.release(). The lock will automatically //get released when program control goes beyond the scope. myMutex::scoped_lock lock;//create a lock lock.acquire(sm);//Method acquire waits until it can acquire a lock on the mutex //***only one thread can access the lines from here...*** ++i;//incrementing i is safe (only one thread can execute the code in this scope) because the mutex locked above protects all lines of code until the lock release. sleep(1);//simply creating a delay to show that no other thread can increment i until release() is executed std::cout<<"In someFunction "<<i<<"\n"; //***...to here*** lock.release();//releases the lock (duh!) } int main() { tbb_thread my_thread1(someFunction);//create a thread which executes 'someFunction' tbb_thread my_thread2(someFunction); tbb_thread my_thread3(someFunction); my_thread1.join();//This command causes the main thread (which is the 'calling-thread' in this case) to wait until thread1 completes its task. my_thread2.join(); my_thread3.join(); }отметим, что
tbb_thread.hустарела. Замена показана здесь.
хотя мьютекс может использоваться для решения других проблем, основная причина их существования заключается в обеспечении взаимного исключения и тем самым решении того, что известно как состояние расы. Когда два (или более) потока или процесса пытаются получить доступ к одной и той же переменной одновременно, у нас есть потенциал для состояния гонки. Рассмотрим следующий код
//somewhere long ago, we have i declared as int void my_concurrently_called_function() { i++; }схематично это выглядит так просто. Это всего лишь одно утверждение. Однако типичный эквивалент языка псевдо-ассемблера может быть:
load i from memory into a register add 1 to i store i back into memoryпоскольку все эквивалентные инструкции на языке ассемблера требуются для выполнения операции приращения на i, мы говорим, что приращение i является неатмоической операцией. Атомарная операция-это операция, которая может быть выполнена на аппаратном обеспечении с гарантией того, что она не будет прервана после начала выполнения инструкции. Приращение i состоит из цепочки из 3 атомарных инструкций. В параллельной системе, где несколько потоков вызывают функцию, возникают проблемы когда поток читает или пишет в неправильное время. Представьте, что у нас есть два потока, работающие одновременно, и один вызывает функцию сразу после другого. Давайте также скажем, что мы инициализировали до 0. Также предположим, что у нас есть много регистров и что два потока используют совершенно разные регистры, поэтому коллизий не будет. Фактическое время этих событий может быть:
thread 1 load 0 into register from memory corresponding to i //register is currently 0 thread 1 add 1 to a register //register is now 1, but not memory is 0 thread 2 load 0 into register from memory corresponding to i thread 2 add 1 to a register //register is now 1, but not memory is 0 thread 1 write register to memory //memory is now 1 thread 2 write register to memory //memory is now 1что случилось, так это то, что у нас есть два потока, увеличивающие i одновременно, наши функция вызывается дважды, но результат не соответствует этому факту. Похоже, что функция была вызвана только один раз. Это происходит потому, что атомарность "сломана" на машинном уровне, то есть потоки могут прерывать друг друга или работать вместе в неправильное время.
нам нужен механизм, чтобы решить эту. Нам нужно наложить некоторый порядок на инструкции выше. Один общий механизм-блокировать все потоки, кроме одного. Мьютекс Pthread использует этот механизм.
какие-нить который должен выполнить некоторые строки кода, которые могут небезопасно изменять общие значения другими потоками в то же время (используя телефон, чтобы поговорить с женой), сначала должен быть сделан получить блокировку на мьютексе. Таким образом, любой поток, требующий доступа к общим данным, должен проходить через блокировку мьютекса. Только тогда поток сможет выполнить код. Этот раздел кода называется критическим разделом.
как только поток выполнил критический раздел, он должен освободить блокировку на мьютексе, чтобы другой поток мог получить блокировку на мьютексе.
концепция наличия мьютекса кажется немного странной при рассмотрении людей, ищущих эксклюзивный доступ к реальным физическим объектам, но при программировании мы должны быть преднамеренными. Параллельные потоки и процессы не имеют социального и культурного воспитания, которое мы делаем, поэтому мы должны заставить их делиться данными красиво.
Итак, технически говоря, как работает мьютекс? Разве он не страдает от той же расы условия, о которых мы говорили ранее? Разве pthread_mutex_lock () не немного сложнее, чем простое приращение переменной?
Технически говоря, нам нужна аппаратная поддержка, чтобы помочь нам. Разработчики оборудования дают нам машинные инструкции, которые делают больше, чем одну вещь, но гарантированно являются атомарными. Классическим примером такой инструкции является test-and-set (TAS). При попытке получить блокировку ресурса мы можем использовать TAS, чтобы проверить, является ли значение в памяти 0. Если это так, то это будет наш сигнал, что ресурс используется, и мы ничего не делаем (или, точнее, мы ждем по какому-то механизму. Мьютекс pthreads помещает нас в специальную очередь в операционной системе и уведомляет нас, когда ресурс становится доступным. Более тупые системы могут потребовать от нас сделать плотную петлю вращения, проверяя условие снова и снова). Если значение в памяти не равно 0, TAS устанавливает местоположение на что-то отличное от 0 без использования каких-либо других инструкций. Это как объединение две инструкции по сборке в 1, чтобы дать нам атомарность. Таким образом, тестирование и изменение значения (если изменение уместно) не может быть прервано после его начала. Мы можем построить мьютексы поверх такой инструкции.
Примечание: некоторые разделы могут выглядеть аналогично более раннему ответу. Я принял его приглашение отредактировать, он предпочел оригинальный способ, поэтому я сохраняю то, что у меня было, которое наполнено немного его словоблудием.
лучшие темы учебник я знаю-это здесь:
https://computing.llnl.gov/tutorials/pthreads/
Мне нравится, что это написано об API, а не о конкретной реализации, и это дает некоторые хорошие простые примеры, чтобы помочь вам понять синхронизацию.
Я недавно наткнулся на этот пост и думаю, что ему нужно обновленное решение для мьютекса стандартной библиотеки c++11 (а именно std::mutex).
я вставил код ниже (мои первые шаги с мьютексом - я узнал параллелизм на win32 с HANDLE, SetEvent, WaitForMultipleObjects и т. д.).
Так как это моя первая попытка с std::mutex и друзьями, я хотел бы видеть комментарии, предложения и улучшения!
#include <condition_variable> #include <mutex> #include <algorithm> #include <thread> #include <queue> #include <chrono> #include <iostream> int _tmain(int argc, _TCHAR* argv[]) { // these vars are shared among the following threads std::queue<unsigned int> nNumbers; std::mutex mtxQueue; std::condition_variable cvQueue; bool m_bQueueLocked = false; std::mutex mtxQuit; std::condition_variable cvQuit; bool m_bQuit = false; std::thread thrQuit( [&]() { using namespace std; this_thread::sleep_for(chrono::seconds(5)); // set event by setting the bool variable to true // then notifying via the condition variable m_bQuit = true; cvQuit.notify_all(); } ); std::thread thrProducer( [&]() { using namespace std; int nNum = 13; unique_lock<mutex> lock( mtxQuit ); while ( ! m_bQuit ) { while( cvQuit.wait_for( lock, chrono::milliseconds(75) ) == cv_status::timeout ) { nNum = nNum + 13 / 2; unique_lock<mutex> qLock(mtxQueue); cout << "Produced: " << nNum << "\n"; nNumbers.push( nNum ); } } } ); std::thread thrConsumer( [&]() { using namespace std; unique_lock<mutex> lock(mtxQuit); while( cvQuit.wait_for(lock, chrono::milliseconds(150)) == cv_status::timeout ) { unique_lock<mutex> qLock(mtxQueue); if( nNumbers.size() > 0 ) { cout << "Consumed: " << nNumbers.front() << "\n"; nNumbers.pop(); } } } ); thrQuit.join(); thrProducer.join(); thrConsumer.join(); return 0; }
функции
pthread_mutex_lock()или приобретает мьютекс для вызывающего потока или блокирует поток до тех пор, пока мьютекс не будет получен. Связанныеpthread_mutex_unlock()освободит мьютекс.думаю мьютекса как очередь, каждый поток, который пытается получить мьютекс будет помещен в конец очереди. Когда поток освобождает мьютекс, следующий поток в очереди и сейчас работает.
A критическая секция относится к области код, где возможен недетерминизм. Часто это потому, что несколько потоков пытаются получить доступ к общей переменной. Критический раздел не безопасен, пока не будет установлена какая-то синхронизация. Блокировка мьютекса является одной из форм синхронизации.
вы должны проверить мьютекс перед использованием переменной, защищенной мьютексом. Таким образом, ваш pthread_mutex_lock() может (в зависимости от реализации) дождаться освобождения mutex1 или вернуть значение, указывающее, что блокировка не может быть получена, если кто-то уже заблокировал ее.
мьютекс на самом деле просто упрощенный семафора. Если Вы читаете о них и понимаете их, вы понимаете мьютексы. Есть несколько вопросов, касающихся мьютексов и семафоров в SO. разница между двоичным семафором и мьютексом,когда мы должны использовать мьютекс и когда мы должны использовать семафор и так далее. В туалет например в первой ссылке есть хороший пример, как можно подумать. Все, что делает код, это проверить, доступен ли ключ, и если он есть, резервирует его. Обратите внимание, что вы на самом деле не зарезервировать сам туалет, но ключ.
СЕМАФОР ПРИМЕР:
sem_t m; sem_init(&m, 0, 0); // initialize semaphore to 0 sem_wait(&m); // critical section here sem_post(&m);Ссылка : http://pages.cs.wisc.edu/~remzi/Classes/537/Fall2008/Notes/threads-semaphores.txt
для тех, кто ищет пример мьютекса shortex:
#include <mutex> using namespace std; int main() { mutex m; m.lock(); // do thread-safe stuff m.unlock(); return 0; }
Comments