(Win32 : WindowsProgramming-19) Thread Synchronization - 2 : Atomic Operation, TLS

Posted by : at

Category : win32   WindowsProgramming


Atomic Operation

#include <stdio.h>
#include <Windows.h>
#include <process.h>

LONG x = 0; // 모든 스레드 공유

UINT __stdcall foo(void* p)
{
    for(int i = 0 i < 1000000; i++)
    {
        x = x + 1;
    }
    return 0;
}

int main()
{
    HANDLE h1 = (HANDLE)_beginthreadex(0, 0, foo, 0, 0, 0);
    HANDLE h2 = (HANDLE)_beginthreadex(0, 0, foo, 0, 0, 0);
    HANDLE h3 = (HANDLE)_beginthreadex(0, 0, foo, 0, 0, 0);

    HANDLE h[3] = {h1, h2, h3};

    WaitForMultipleObjects(3, h, TRUE, INFINITE);
    printf("result : %d\n", x);
    return 0;
}
UINT __stdcall foo(void* p)
{
    for(int i = 0 i < 1000000; i++)
    {
        // x = x + 1;
        __asm
        {
            mov eax, x  // 1번 thread가 eax로 x값을 가져간 상태에서
            add eax, 1
            mov x, eax  // 2번 thread가 x에 eax에 1을 더한값을 넣는다면? -> 이러한이유로 원하는 값이 안나옴
        }
    }
    return 0;
}

해결책?

UINT __stdcall foo(void* p)
{
    for(int i = 0 i < 1000000; i++)
    {
        // x = x + 1;
        __asm
        {
            inc x       // 이 어셈블리 명령은 어셈블리 명령중 다른 thread가 사용하지 못하게 함.
            // 단, muti CPU를 사용할 경우 물리적으로 CPU가 다르기에 역시 정상적으로 동작하지 않음.
        }
    }
    return 0;
}
UINT __stdcall foo(void* p)
{
    for(int i = 0 i < 1000000; i++)
    {
        // x = x + 1;
        __asm
        {
            lock inc x      // 아에 lock을 걸어야한다.
        }
    }
    return 0;
}

무조건 어셈블리를 써야하나? -> Nope

UINT __stdcall foo(void* p)
{
    for(int i = 0 i < 1000000; i++)
    {
        // x = x + 1;
        InterlockedIncreament(&x);  // 내부적으로 lock inc를 사용
        // (참고) 브레이크 포인트를 여기 걸고 Alt+F8을 누르면, 기게어코드를 보여줌.
    }
    return 0;
}
  • Atomic operation
    • InterlockedIncreament
    • InterlockedXXX

Thread Local Storage

#include <stdio.h>
#include <Windows.h>
#include <tchar.h>
#include <process.h>

// 호출할때 마다 3의 배수를 차례대로 반환
int next3times()
{
    static int n = 0;
    n = n + 3;
    return n;
}

UINT __stdcall foo(void* p)
{
    printf("%s : %d\n", next3times());  // 3
    printf("%s : %d\n", next3times());  // 6
    printf("%s : %d\n", next3times());  // 9
    return 0;
}

int main()
{
    HANDLE h1 = (HANDLE)_beginthreadex(0, 0, foo, (void*)"A", 0, 0);
    HANDLE h2 = (HANDLE)_beginthreadex(0, 0, foo, (void*)"\tB", 0, 0);
    getchar();
    return 0;
}

결과는?

    B : 3
    B : 6
    B : 9
A : 3
A : 12
A : 15

매번 이렇진 않지만 어쨋든 예상했던 결과와는 다르게 나온다.

Thread별로 next3times()가 동작하게 해보자(Thread당 data(static) 메모리를 갖게하자) -> Thread Local Storage

int next3times()
{
    // TLS(Thread Local Storage)에 넣어달라
    __declspec(thread) static int n = 0;
    n = n + 3;
    return n;
}
    B : 3
    B : 6
    B : 9
A : 3
A : 6
A : 9

__declspec(thread)는 지역변수에 붙일 수 없다. static전역, 전역변수에만 붙일 수 있음.


About Taehyung Kim

안녕하세요? 8년차 현업 C++ 개발자 김태형이라고 합니다. 😁 C/C++을 사랑하며 다양한 사람과의 협업을 즐깁니다. ☕ 꾸준한 자기개발을 미덕이라 생각하며 노력중이며, 제가 얻은 지식을 홈페이지에 정리 중입니다. 좀 더 상세한 제 이력서 혹은 Private 프로젝트 접근 권한을 원하신다면 메일주세요. 😎

Star
Useful Links