ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • C++ 멀티스레드, Atomic
    C++/C++ 멀티스레드 2022. 10. 5. 23:22
    728x90

    멀티스레드 환경에서의 작업은 동시에 다양한 작업을 가능케 하지만,

    여러가지 문제도 존재하고, 그러한 문제들이 멀티스레드 프로그래밍을 어렵게 한다.

     

    여러가지 문제점들 중 한 가지가 공유데이터에 대한 동시접근시 발생하는 문제다.

    #include <thread>
    #include <iostream>
    #include <atomic>
    
    int sum = 0;
    
    void Add()
    {
    	for (int i = 0; i < 100000; i++)
    	{
    		sum++;
    	}
    }
    
    void Sub()
    {
    	for (int i = 0; i < 100000; i++)
    	{
    		sum--;
    	}
    }
    
    void main()
    {
    	Add();
    	Sub();
    	cout << sum << endl;
    }

    이런 코드가 있다고 하자.

    결과는 당연하게도 0이 나온다.

     

    이 코드를 멀티스레드 환경에서 실행해보자.

    void Add()
    {
    	for (int i = 0; i < 100000; i++)
    	{
    		sum++;
    	}
    }
    
    void Sub()
    {
    	for (int i = 0; i < 100000; i++)
    	{
    		sum--;
    	}
    }
    
    void main()
    {
    	std::thread t1(Add);
    	std::thread t2(Sub);
    
    	t1.join();
    	t2.join();
    
    	cout << sum << endl;
    }

    이렇게 실행하면 결과는 0이 아닐뿐더러, 실행할 때마다 다른 결과가 나온다.

     

    그 이유에 대해서 살펴보자.

     

    sum++과 sum--는 cpp코드에서는 한 줄로 실행되는 것처럼 보이지만, 실제로는 그렇지 않다.

    어셈블리코드를 열어보면 알 수 있지만, 간단하게 설명하자면

    1. sum값을 가져와서 임시변수 A에 저장한다.

    2. A에 저장된 값을 1 증가시킨다.

    3. A의 값을 sum에 저장한다.

     

    A = sum

    A = A + 1

    sum=A

     

    이러한 과정으로 sum++가 실행된다.

    그러다 보니 여러 스레드에 의해서 공유변수 sum에 대해 sum++과 sum--가 동시에 실행되면

    아래와 같은 문제가 발생한다. 발생할 수 있다.

    No 스레드 t1 A 스레드 t2 B sum
    1 A=sum 0     0
    2     B=sum 0 0
    3 A=A+1 1      
    4 sum=A       1
    5     B=B-1 -1  
    6     sum=B   -1

     

    sum을 한 번 더하고, 한 번 뺐으면 결과로 0이 나와야 할 것 같은데

    sum은 -1이 나온다.

     

    이 결과는 스레드가 각 명령을 실행하는 순서에 따라서 0이 될수도 있고, 1이될수도, -1이 될 수도 있다.

     

    여튼... 공유데이터에 대한 접근에 대해서 관리를 해주지 않으면, 데이터가 의도한대로 나오지 않는 문제가 발생할 수 있다.

     

    다양한 해결 방법이 있지만 여기서는 atomic에 대해서 살펴보겠다.

     


    Atomic

    All or Nothing

    무슨 뜻이냐면, 전부 실행하거나 하나도 실행하지 않거나 라는 뜻이다.

    앞서 sum++은 실제로

    1. A = sum

    2. A = A + 1

    3. sum=A

    세 단계로 실행된다는 것을 알아봤다.

     

    Atomic하게 실행하면, 이 세 단계를 전부 실행하거나 실행하지 않는다는 의미이다.

    즉, sum++의 2단계정도 수행하고, 다른 스레드가 sum에 대해서 접근하려고 하면,

    sum++에 대한 연산을 끝낼때 까지 다른 스레드의 접근을 막거나(all)

    sum++가 지금까지 했던 연산 전부를 취소하는 것이다(nothing)

     

    예를 들어보자

    M이라는 게임에서 두 유저가 교환을 한다고 해보자

    A가 B의 아이템을 100 게임머니에 사는 상황이라면

    1. A가 B에게 100게임머니를 지급함

    2. B가 A에게 아이템을 지급함

    이렇게 설명할 수 있을 것이다.

     

    이러한 작업을 처리할 때는 Atomic하게 실행해야한다.

    만약 Atomic하게 하지 않았는데 A가 B에게 100 게임머니를 지급하는 순간 서버가 다운됐다면,

    A는 100게임머니를 잃었는데, 그 100게임머니는 B에게 들어가지도 않은 상황이 될것이다.

     

    그러면 C++에서는 어떻게 구현하는지 알아보자.

    간단하다 변수 선언부만 조금 바꿔주면 된다.

     

    atomic<int> sum;
    
    void Add()
    {
    	for (int i = 0; i < 100000; i++)
    	{
    		sum++;
    	}
    }
    
    void Sub()
    {
    	for (int i = 0; i < 100000; i++)
    	{
    		sum--;
    	}
    }
    
    void main()
    {
    	std::thread t1(Add);
    	std::thread t2(Sub);
    
    	t1.join();
    	t2.join();
    
    	cout << sum << endl;
    }

    이렇게 하면, 여러번 실행하더라도 결과가 0이 나온다.

     

    추가로 atomic을 사용할 때 사용하는 함수가 몇 가지 더 있다.

    atomic<int> sum;
    
    void Add()
    {
    	for (int i = 0; i < 100000; i++)
    	{
    		sum.fetch_add(1);
    	}
    }
    
    void Sub()
    {
    	for (int i = 0; i < 100000; i++)
    	{
    		sum.fetch_sub(1);
    	}
    }
    
    void main()
    {
    	std::thread t1(Add);
    	std::thread t2(Sub);
    
    	t1.join();
    	t2.join();
    
    	cout << sum << endl;
    }

    fetch_add와 fetch_sub와 같이 atomic변수가 사용할 수 있는 전용 덧셈 뺄셈이다.

    사용하지 않아도 결과가 잘 나온다.

    이 외에도 and, or, xor등의 함수도 있다.

     

     


    그럼 공유데이터에 대한 접근시 atomic만 쓰면 되나??

     

    꼭 그렇지는 않다.

    atomic 변수에 대한 연산은 속도가 상당히 느리다.

    뿐만 아니라 이것 말고도 다른 동기화 기법들도 존재한다.(고 한다)

    여튼 atomic변수는 꼭 필요할 때만 사용하는게 좋겠다.

     

    728x90

    'C++ > C++ 멀티스레드' 카테고리의 다른 글

    C++ SpinLock구현  (0) 2022.10.09
    멀티스레드 Lock구현  (0) 2022.10.09
    C++, 멀티스레드 교착상태(DeadLock)  (0) 2022.10.08
    C++ 멀티스레드, lock(mutex)  (1) 2022.10.06
    C++ 스레드 생성  (0) 2022.10.05

    댓글

Designed by Tistory.