뮤텍스 예제 / 튜토리얼? [닫은]


176

멀티 스레딩을 처음 접했고 뮤텍스의 작동 방식을 이해하려고했습니다. 많은 인터넷 검색을 수행했지만 잠금이 작동하지 않는 자체 프로그램을 만들었으므로 여전히 작동 방식에 대한 의구심을 남겼습니다.

뮤텍스의 비 직관적 인 구문 중 하나는 pthread_mutex_lock( &mutex1 );, 실제로 잠금하려는 것이 다른 변수 일 때 뮤텍스가 잠겨있는 것처럼 보입니다. 이 구문은 뮤텍스를 잠그면 뮤텍스가 잠금 해제 될 때까지 코드 영역을 잠그는 것을 의미합니까? 그렇다면 스레드가 영역이 잠겨 있음을 어떻게 알 수 있습니까? [ 업데이트 : 스레드는 메모리 펜싱에 의해 영역이 잠겨 있음을 알고 있습니다 ]. 그리고 그러한 현상을 비판적 섹션이라고 부르지 않습니까? [ 업데이트 : 중요 섹션 객체는 Windows에서만 사용할 수 있으며, 객체는 뮤텍스보다 빠르며이를 구현하는 스레드에서만 볼 수 있습니다. 그렇지 않으면, 임계 섹션은 단지 뮤텍스에 의해 보호되는 코드 영역을 나타냅니다 ]

요컨대, 가장 간단한 mutex 예제 프로그램 과 그 작동 방식의 논리에 대한 가장 간단한 설명 을 도와 주실 수 있습니까? 나는 이것이 다른 많은 초보자들에게 도움이 될 것이라고 확신합니다 .


2
간단한 튜토리얼 (스레드, tbb 또는 pthread를 향상시키는 지)의 필요성을 강조하기 위해 계속 : 혼란의 예 : 1. stackoverflow.com/questions/3528877/… 2. stackoverflow.com/questions/2979525/… 3. stackoverflow.com/questions/2095977/to-mutex 또는
not

1
나는 이것을 불쾌하게 의미하지는 않지만, 마지막 의견에서 나에게 제안하는 것은 뮤텍스의 작동 방식과 왜 필요한지에 대한 유추와 기술적 인 설명이 덜 필요하다는 것입니다.
San Jacinto

@ San : 공격을받지 않았다 :) 나의 의견은 초보자가 뮤텍스에 대해 가장 짧고 가장 명확한 설명을 얻을 수 있다고 제안하기위한 것이었다. 많은 비유가 초보자에게 혼란을 줄 수 있으므로 다른 비유를 별도로 유지해야합니다. 내가 질문과 답변을 게시하는 모든 이유는 초보자로서 긴 설명과 코드 샘플을 읽는 것이 고통 스럽기 때문입니다. 다른 사람이 고통을 겪고 싶지는 않습니다.
Nav

2
@Cory :이 답변을 개선 할 수 있다면 제안 해 드리겠습니다. 다른 많은 사람들이 도움이 된 것을 알게되어 기쁩니다. 도움이되지 않으면 다른 뮤텍스 자습서를 지적한 다른 사람들의 답변도 있습니다. 왜 그렇게 부정적입니까?
Nav

답변:


278

전 세계의 초보자에게 개념을 설명하려는 겸손한 시도가 있습니다. ( 블로그 의 색상 코드 버전 )

많은 사람들이 고독한 전화 부스 (휴대 전화가없는)를 방문하여 사랑하는 사람들과 이야기를 나눕니다. 부스의 문 손잡이를 잡은 첫 번째 사람은 전화를 사용할 수있는 사람입니다. 그는 전화를 사용하는 한 문 손잡이를 계속 잡고 있어야합니다. 그렇지 않으면 다른 사람이 손잡이를 잡고 밖으로 내던져 아내와 대화 할 것입니다. :) 대기열 시스템이 없습니다. 상대방이 전화를 마치고 부스에서 나와 도어 핸들을 나가면 도어 핸들을 잡을 다음 사람이 전화를 사용할 수 있습니다.

스레드 입니다 : 각 사람 뮤텍스 이다 : 문이 처리 잠금이 사람의 손이 :이다 자원이 있다 : 전화


전화를 사용하여 아내와 대화하기 위해 다른 스레드에서 동시에 수정해서는 안되는 코드 줄을 실행 해야하는 스레드는 먼저 뮤텍스에 대한 잠금을 획득해야합니다 (부스의 도어 핸들 클러치) ). 그래야만 스레드가 해당 코드 줄을 실행할 수 있습니다 (전화 걸기).

스레드가 해당 코드를 실행하면 다른 스레드가 뮤텍스에 대한 잠금을 획득 할 수 있도록 뮤텍스의 잠금을 해제해야합니다 (다른 사람들이 전화 부스에 액세스 할 수 있음).

[ 뮤텍스를 갖는 개념은 실제 배타적 액세스를 고려할 때 약간 불합리하지만 프로그래밍 세계에서는 다른 스레드가 스레드가 이미 일부 코드 라인을 실행하고 있다고 볼 수있는 다른 방법이 없다고 생각합니다. 재귀 뮤텍스 등의 개념이 있지만이 예제는 기본 개념을 보여주기위한 것입니다. 예제가 개념의 명확한 그림을 제공하기를 바랍니다. ]

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, 당신은 괄호를 사용하여 다음과 같이 당신이 범위의 잠금 사용하는 경우, 이 제공하는 이점에 대한을 . 범위가 지정된 잠금은 약간의 성능 오버 헤드가 있습니다.


2
@ 산 : 솔직히 말하겠습니다. 예, 당신이 완전한 초보자에게 세부 사항을 설명하기 위해 최선을 다했다는 사실을 좋아합니다. 그러나 (나를 오해하지 마십시오)이 게시물의 의도는 개념을 간단한 설명 으로 작성하는 것이 었습니다 (다른 답변은 긴 자습서를 지적했습니다). 전체 답변을 복사하여 별도의 답변으로 게시하도록 요청해도 괜찮지 않겠습니까? 귀하의 답변을 가리 키도록 답변을 롤백하고 편집 할 수 있습니다.
Nav

2
@Tom이 경우 해당 뮤텍스에 액세스하면 안됩니다. 보호 대상이 무엇이든 그러한 고요한 환경으로부터 보호되도록 작동을 캡슐화해야합니다. 라이브러리의 노출 된 API를 사용할 때 라이브러리가 스레드 안전성을 보장하는 경우, 고유 한 공유 항목을 보호하기 위해 다른 뮤텍스를 포함하는 것이 안전합니다. 그렇지 않으면 제안한대로 실제로 새로운 문 손잡이를 추가하고 있습니다.
San Jacinto

2
내 요점을 확장하려면 부스 주변에 더 큰 공간을 추가해야합니다. 객실에는 화장실과 샤워 실이 있습니다. 한 번에 한 사람 만 방에 허용된다고 가정 해 봅시다. 이 방에는 전화 부스와 같이 방 출입을 보호하는 손잡이가있는 문이 있어야합니다. 이제 추가 뮤텍스가 있더라도 모든 프로젝트에서 전화 부스를 재사용 할 수 있습니다. 또 다른 옵션은 회의실의 각 장치에 대한 잠금 메커니즘을 노출하고 회의실 클래스의 잠금을 관리하는 것입니다. 어느 쪽이든 같은 객체에 새 잠금을 추가하지 않습니다.
San Jacinto

8
C ++ 11 스레딩 예제 가 잘못되었습니다 . TBB도 마찬가지입니다. 단서의 이름 범위는 lock 입니다.
Jonathan Wakely

3
@Jonathan이라는 두 가지를 잘 알고 있습니다. 내가 쓴 문장을 놓친 것 같습니다 (could've shown scoped locking by not using acquire and release - which also is exception safe -, but this is clearer. 범위 잠금을 사용하는 경우 구축하는 응용 프로그램의 종류에 따라 개발자의 책임입니다. 이 답변은 뮤텍스 개념에 대한 기본적인 이해를 다루고 그것의 모든 복잡성을 다루지 않기 위해 귀하의 의견과 링크는 환영하지만이 튜토리얼의 범위를 벗어났습니다.
Nav

41

뮤텍스가 다른 문제를 해결하는 데 사용될 수 있지만 이들이 존재하는 주된 이유는 상호 배제를 제공하여 경쟁 조건으로 알려진 것을 해결하는 것입니다. 두 개 이상의 스레드 또는 프로세스가 동일한 변수에 동시에 액세스하려고하면 경쟁 조건이 발생할 수 있습니다. 다음 코드를 고려하십시오

//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

일어난 일은 동시에 두 개의 스레드가 증가하고 함수가 두 번 호출되지만 결과는 그 사실과 일치하지 않습니다. 함수가 한 번만 호출 된 것 같습니다. 이는 기계 수준에서 원 자성이 "파손"되었기 때문에 스레드가 서로 방해하거나 잘못된 시간에 함께 작동 할 수 있습니다.

이를 해결하기위한 메커니즘이 필요합니다. 위의 지침에 따라 주문을해야합니다. 하나의 일반적인 메커니즘은 하나를 제외한 모든 스레드를 차단하는 것입니다. Pthread mutex는이 메커니즘을 사용합니다.

전화로 아내와 대화하기 위해 다른 스레드가 공유 값을 안전하지 않게 수정할 수있는 일부 코드 행을 실행해야하는 스레드는 먼저 뮤텍스에 대한 잠금을 획득해야합니다. 이러한 방식으로 공유 데이터에 액세스해야하는 모든 스레드는 뮤텍스 잠금을 통과해야합니다. 그래야만 스레드가 코드를 실행할 수 있습니다. 이 코드 섹션을 중요 섹션이라고합니다.

스레드가 중요한 섹션을 실행하면 다른 스레드가 뮤텍스에 대한 잠금을 획득 할 수 있도록 뮤텍스에 대한 잠금을 해제해야합니다.

실제 물리적 객체에 독점적으로 액세스하려는 인간을 고려할 때 뮤텍스를 갖는 개념은 약간 이상하게 보이지만 프로그래밍 할 때는 의도적이어야합니다. 동시 스레드와 프로세스는 우리가하는 사회적, 문화적 양육이 없으므로 데이터를 잘 공유하도록 강요해야합니다.

기술적으로 말하자면 뮤텍스는 어떻게 작동합니까? 앞에서 언급 한 것과 동일한 경쟁 조건으로 고통받지 않습니까? pthread_mutex_lock ()은 변수의 단순한 증분보다 조금 더 복잡하지 않습니까?

기술적으로 말하면, 우리를 돕기 위해 약간의 하드웨어 지원이 필요합니다. 하드웨어 설계자들은 하나 이상의 작업을 수행하지만 원 자성이 보장되는 기계 명령어를 제공합니다. 이러한 명령어의 전형적인 예는 TAS (Test-and-Set)입니다. 리소스에 대한 잠금을 얻으려고 할 때 TAS는 메모리의 값이 0인지 확인하기 위해 사용할 수 있습니다. 만약 그렇다면, 리소스가 사용 중이고 아무것도하지 않는다는 신호일 것입니다. pthreads mutex는 우리를 운영 체제의 특수 대기열에 넣고 리소스를 사용할 수있게되면 알려줄 것입니다. . 메모리의 값이 0이 아닌 경우 TAS는 다른 명령을 사용하지 않고 위치를 0 이외의 다른 위치로 설정합니다. 그것' 두 개의 조립 명령을 1로 결합하여 원 자성을 부여하는 것과 같습니다. 따라서 값이 변경되면 테스트 및 변경 (적절한 경우)을 중단 할 수 없습니다. 그러한 명령 위에 뮤텍스를 만들 수 있습니다.

참고 : 일부 섹션은 이전 답변과 유사하게 나타날 수 있습니다. 나는 편집에 대한 그의 초대를 받아 들였고, 원래의 방식을 선호했기 때문에 내가 가진 것을 조금 그의 언어로 주입하고 있습니다.


1
정말 감사합니다, 샌 나는 당신의 답변에 연결했습니다 :) 실제로, 나는 당신이 흐름을 유지하기 위해 당신이 나의 답변 + 당신의 답변을 취하여 별도의 답변으로 게시하도록 의도했습니다. 내 대답의 일부를 재사용해도 괜찮습니다. 우리는 어쨌든 우리 자신을 위해 이것을하지 않습니다.
Nav

13

내가 아는 최고의 스레드 자습서는 다음과 같습니다.

https://computing.llnl.gov/tutorials/pthreads/

나는 특정 구현이 아닌 API에 대해 작성되었으며 동기화를 이해하는 데 도움이되는 멋진 간단한 예제를 제공합니다.


나는 그것이 좋은 튜토리얼이라고 동의하지만, 한 페이지에 많은 정보가 있으며 프로그램은 길다. 내가 게시 한 질문은 "나는 꿈이있다"연설의 뮤텍스 버전입니다. 초보자는 뮤텍스에 대해 배우고 직관적이지 않은 구문이 어떻게 작동하는지 이해하는 간단한 방법을 찾을 수 있습니다 (모든 튜토리얼에서 부족한 설명 중 하나임) .
Nav

7

나는 최근 에이 게시물을 우연히 발견했으며 표준 라이브러리의 c ++ 11 뮤텍스 (즉 std :: mutex)에 대한 업데이트 된 솔루션이 필요하다고 생각합니다.

아래 코드를 붙여 넣었습니다 (뮤텍스를 사용한 첫 단계-HANDLE, SetEvent, WaitForMultipleObjects 등을 사용하여 win32에서 동시성을 배웠습니다).

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;
}

1
감독자! 게시 해 주셔서 감사합니다. 이전에 언급했듯이 뮤텍스의 개념을 간단히 설명하는 것이 었습니다. 다른 모든 튜토리얼은 생산자 소비자 및 조건 변수 등의 추가 개념으로 인해 매우 어려워서 지구상에서 무슨 일이 일어나고 있는지 이해하기가 매우 어려웠습니다.
Nav

4

함수는 pthread_mutex_lock()어느 취득 뮤텍스까지 스레드가 획득 될 수 호출 스레드 또는 블록 뮤텍스. 관련 pthread_mutex_unlock()이 뮤텍스를 해제합니다.

뮤텍스를 대기열로 생각하십시오. 뮤텍스를 얻으려고 시도하는 모든 스레드는 큐의 끝에 배치됩니다. 스레드가 뮤텍스를 해제하면 큐의 다음 스레드가 해제되어 현재 실행 중입니다.

임계 영역은 비 - 결정 성이 가능 코드의 영역을 말한다. 여러 스레드가 공유 변수에 액세스하려고 시도하는 경우가 종종 있습니다. 중요한 섹션은 일종의 동기화가 이루어질 때까지 안전하지 않습니다. 뮤텍스 잠금은 동기화의 한 형태입니다.


1
다음에 시도하는 실이 정확하게 들어가는 것이 보장됩니까?
Arsen Mkrtchyan

1
@Arsen 보장하지 않습니다. 그것은 단지 유용한 비유 일뿐입니다.
chrisaycock

3

뮤텍스로 보호되는 영역을 사용하기 전에 뮤텍스 변수를 확인해야합니다. 따라서 pthread_mutex_lock ()은 (구현에 따라) mutex1이 해제 될 때까지 기다리거나 다른 사람이 이미 잠근 경우 잠금을 얻을 수 없음을 나타내는 값을 반환 할 수 있습니다.

뮤텍스는 실제로 단순화 된 세마포입니다. 당신이 그들에 대해 읽고 이해한다면, 당신은 뮤텍스를 이해합니다. SO의 뮤텍스와 세마포어에 관한 몇 가지 질문이 있습니다. 바이너리 세마포어와 뮤텍스의 차이점 , 언제 뮤텍스 를 사용해야하고 언제 세마포어를 사용해야 하는가 등 . 첫 번째 링크의 화장실 예는 생각할 수있는만큼 좋은 예입니다. 모든 코드는 키가 사용 가능한지 확인하고 사용 가능한 경우 예약합니다. 화장실 자체를 예약하지 않고 열쇠를 예약하십시오.


1
pthread_mutex_lock다른 사람이 자물쇠를 들고 있으면 돌아올 수 없습니다. 이 경우에는 차단되며 요점입니다. pthread_mutex_trylock잠금이 유지되면 반환되는 함수입니다.
R .. GitHub 중지 지원 얼음 얼음

1
네, 처음에 이것이 어떤 구현인지 알지 못했습니다.
Makis

3

shortex mutex 예제를 찾는 사람들에게 :

#include <mutex>

int main() {
    std::mutex m;

    m.lock();
    // do thread-safe stuff
    m.unlock();
}

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.