티스토리 뷰

프로그래밍/MFC

쓰레드 TLS

에어버스 2018. 12. 13. 23:51

Thread TLS(Thread Local Storage) 쓰레드 TLS, 스레드 TLS

TLS : 쓰레드 안에서 서로 다른 함수들이 공유하는 전역변수를 말한다.

1. WIN32 TLS

프로세스는 TLS Bit Flag 라는 배열을 1개 갖고, 각 쓰레드마다 TLS 배열을 가지고 있다. 이때 모든 배열의 크기는 동일하며, Win95/NT 는 64개, Win98/Me 는 80개, Win2000/XP 는 1088 개의 배열 크기를 갖는다.

쓰레드가 TLS변수를 사용하려면 TlsAlloc() 로 TLS BIT FLAG 라는 배열 요소값이 FREE(사용하지 않는) 를 찾아 INUSE로 바꾸고, 인덱스를 반환한다. 그러면 다른 쓰레드는 반환된 인덱스를 사용하지 못하고, 할당 받은 쓰레드에서만 사용 가능하다. 그래서 쓰레드간 충돌이 발생할 수 없다.

TlsSetValue(반환된 인덱스, 32bit 값) 으로 쓰레드 TLS에 값을 저장하고, TleGetValue(인덱스) 로 값을 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
DWORD g_dwTls = TLS_OUT_OF_INDEXES; // 외부변수로 선언
 
UINT DrawBall1(LPVOID pVoid);
 
CThreadView::~CThreadView()
{
    TlsFree(g_dwTls);
}
 
void CThreadView::OnLButtonDown(UINT nFlags, CPoint point)
{
    AfxBeginThread(DrawBall1, GetSafeHwnd());
    CView::OnLButtonDown(nFlags, point);
}
 
UINT DrawBall1(LPVOID pVoid)
{
    HWND hWnd = (HWND)pVoid;
    HDC hDC = GetDC(hWnd);
    srand((UINT)time(NULL));
    COLORREF colBall = RGB(rand() % 256, rand() % 256, rand() % 256);
    HBRUSH hBr = CreateSolidBrush(colBall);
    HBRUSH hOLDBr = (HBRUSH)SelectObject(hDC, hBr);
    POINT pt = { 00 };
    g_dwTls = TlsAlloc(); // 사용 가능한 인덱스 할당
    TlsSetValue(g_dwTls, (LPVOID)5);
    int xInc = int(TlsGetValue(g_dwTls)), yInc = int(TlsGetValue(g_dwTls));//int xInc = 5, yInc = 5;
    RECT rcPreBall, rcBall;
    for (int w = 0; w < 100;)
    {
        Sleep(10);
        SetRect(&rcPreBall, pt.x - 10, pt.y - 10, pt.x + 10, pt.y + 10);
        pt.x += xInc;
        pt.y += yInc;
        SetRect(&rcBall, pt.x - 10, pt.y - 10, pt.x + 10, pt.y + 10);
        FillRect(hDC, &rcPreBall, (HBRUSH)GetStockObject(WHITE_BRUSH));
        Ellipse(hDC, rcBall.left, rcBall.top, rcBall.right, rcBall.bottom);
        RECT rcCli;
        GetClientRect(hWnd, &rcCli);
        if (rcCli.right <= pt.x)
            xInc = -5, w++;
        else if (pt.x <= rcCli.left)
            xInc = 5, w++;
        if (rcCli.bottom <= pt.y)
            yInc = -5, w++;
        else if (pt.y <= rcCli.top)
            yInc = 5, w++;
    }
    FillRect(hDC, &rcBall, (HBRUSH)GetStockObject(WHITE_BRUSH));
    SelectObject(hDC, hOLDBr);
    DeleteObject(hBr);
    ReleaseDC(hWnd, hDC);
    return 0;
}
cs

마우스 왼쪽버튼을 클릭할때마다 쓰레드 생성하고, 쓰레드 안에서 TLS에 값을 저장하고, 저장된 값을 가져와 볼을 값만큼 이동시킨다.

다른 TLS도 동일하게, 만약 저장할 값이 32bit 보다 크면 Heap 할당 후 주소를 저장하고, Heap 해제하면 된다.

마우스 왼쪽 버튼을 여러번 클릭하면 각 쓰레드마다 인덱스 값이 달라 서로 충돌 염려가 없으나 사용할 수 있는 TLS 변수 갯수 OS버전마다 제한적이다.

2. 컴파일러 지원 TLS 

1
__declspec(thread) 데이터형 변수명;
cs

위 처럼 외부변수 처럼 선언만 하고 사용한다.
만약, 데이터 값이 큰 경우 Heap 할당하고, Heap 해제만 하면된다.
컴파일러에서 지원하는 것이라 사용이 편하지만, Visual C++ 4.0 이상에서 지원하고 선언된 변수는 모든 쓰레드에 동일한 이름으로 존재한다고 생각하면 된다. 컴파일러는 __declspec(thread) 로 시작하는 모든 변수를 컴파일 단계에서 .tls 섹션으로 통합한다. 이렇게 만들어진 App이 실행된 다음 쓰레드가 생성될때 OS는 생성되는 쓰레드에 .tls 섹션 블록을 동적으로 할당하고, .tls 섹션 블록에 안전하게 접근하도록 접근 코드를 삽입한다.

단점>
- TLS를 전혀 필요치 않는 완전한 별개의 쓰레드에 대해서도 OS는 무조건 .tls 블록을 할당하기 때문에 메모리 낭비된다.
- 쓰레드가 실행되면서 __declspec(thread) 변수에 접근할때 접근하는 쓰레드에 할당된 .tls 에 안전하게 접근할 수 있도록 컴파일러가 삽입한 코드가 실핼되기 때문에 오버헤드(코드 크기, 실행속도)가 크다.
- 명시적으로 로드되는 DLL 안에서는 컴파일러 지원 TLS를 사용할 수 없다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
__declspec(threadint g_dwTls = 0;
 
UINT DrawBall1(LPVOID pVoid);
void CThreadView::OnLButtonDown(UINT nFlags, CPoint point)
{
    AfxBeginThread(DrawBall1, GetSafeHwnd());
    CView::OnLButtonDown(nFlags, point);
}
 
UINT DrawBall1(LPVOID pVoid)
{
    HWND hWnd = (HWND)pVoid;
    HDC hDC = GetDC(hWnd);
    srand((UINT)time(NULL));
    COLORREF colBall = RGB(rand() % 256, rand() % 256, rand() % 256);
    HBRUSH hBr = CreateSolidBrush(colBall);
    HBRUSH hOLDBr = (HBRUSH)SelectObject(hDC, hBr);
    POINT pt = { 00 };
    g_dwTls = 5// 값 저장, 전역변수 처럼 
    int xInc = g_dwTls, yInc = g_dwTls; // 읽어와서 사용하면 된다.
    RECT rcPreBall, rcBall;
    for (int w = 0; w < 100;)
    {
        Sleep(10);
        SetRect(&rcPreBall, pt.x - 10, pt.y - 10, pt.x + 10, pt.y + 10);
        pt.x += xInc;
        pt.y += yInc;
        SetRect(&rcBall, pt.x - 10, pt.y - 10, pt.x + 10, pt.y + 10);
        FillRect(hDC, &rcPreBall, (HBRUSH)GetStockObject(WHITE_BRUSH));
        Ellipse(hDC, rcBall.left, rcBall.top, rcBall.right, rcBall.bottom);
        RECT rcCli;
        GetClientRect(hWnd, &rcCli);
        if (rcCli.right <= pt.x)
            xInc = -5, w++;
        else if (pt.x <= rcCli.left)
            xInc = 5, w++;
        if (rcCli.bottom <= pt.y)
            yInc = -5, w++;
        else if (pt.y <= rcCli.top)
            yInc = 5, w++;
    }
    FillRect(hDC, &rcBall, (HBRUSH)GetStockObject(WHITE_BRUSH));
    SelectObject(hDC, hOLDBr);
    DeleteObject(hBr);
    ReleaseDC(hWnd, hDC);
    return 0;
}
cs

 

 3. MFC TLS

WIN32 TLS 의 불편함과 갯수 제한을 없애기 위해 만들어 졌다.
선언만으로 사용 가능하다.

CNoTrackObject를 상속받는 구조체나 클래스 안에 사용할 변수를 추가해서 사용한다. (아래 코드 1~4행 참고)

1
2
3
// afxtls_.h
#define THREAD_LOCAL(class_name, ident_name) \
    AFX_COMDAT CThreadLocal<class_name> ident_name;
cs

아래 코드 4행에서 THREAD_LOCAL 매크로는 위 코드처럼 mfcTls형 CThreadLocal 템플릿 변수로 pTls를 만든다. 내부적으로는 mfcTls 객체를 동적으로 만들고 pTls-> 로 데이터를 접근해서 사용하며, 자동으로 메모리 해제된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
struct mfcTls : public CNoTrackObject {
    int dwTls;
};
THREAD_LOCAL(mfcTls, pTls) // mfcTls 는 CThreadLocalObject 타입 변수
 
UINT DrawBall1(LPVOID pVoid)
{
    HWND hWnd = (HWND)pVoid;
    HDC hDC = GetDC(hWnd);
    srand((UINT)time(NULL));
    COLORREF colBall = RGB(rand() % 256, rand() % 256, rand() % 256);
    HBRUSH hBr = CreateSolidBrush(colBall);
    HBRUSH hOLDBr = (HBRUSH)SelectObject(hDC, hBr);
    POINT pt = { 00 };
    pTls->dwTls = 5;
    int xInc = pTls->dwTls, yInc = pTls->dwTls; //int xInc = 5, yInc = 5;
    RECT rcPreBall, rcBall;
    for (int w = 0; w < 100;)
    {
        Sleep(10);
        SetRect(&rcPreBall, pt.x - 10, pt.y - 10, pt.x + 10, pt.y + 10);
        pt.x += xInc;
        pt.y += yInc;
        SetRect(&rcBall, pt.x - 10, pt.y - 10, pt.x + 10, pt.y + 10);
        FillRect(hDC, &rcPreBall, (HBRUSH)GetStockObject(WHITE_BRUSH));
        Ellipse(hDC, rcBall.left, rcBall.top, rcBall.right, rcBall.bottom);
        RECT rcCli;
        GetClientRect(hWnd, &rcCli);
        if (rcCli.right <= pt.x)
            xInc = -5, w++;
        else if (pt.x <= rcCli.left)
            xInc = 5, w++;
        if (rcCli.bottom <= pt.y)
            yInc = -5, w++;
        else if (pt.y <= rcCli.top)
            yInc = 5, w++;
    }
    FillRect(hDC, &rcBall, (HBRUSH)GetStockObject(WHITE_BRUSH));
    SelectObject(hDC, hOLDBr);
    DeleteObject(hBr);
    ReleaseDC(hWnd, hDC);
    return 0;
}
cs

단점>
- MFC App에서만 사용 가능
- WIN32 TLS에 비해 오버헤드가 크다.

실행결과> 마우스 왼쪽 버튼을 여러번 클릭한다. (각 쓰레드 마다 공이 1개씩 그려진다)

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30