출처 : http://www.gloomycoder.com/archives/127

내용 정리가 잘 되어 있어서 퍼왔습니다. 가장 아래 부분에 EnterCriticalSection(...) 호출시 예외가 발생하는 경우 같은 설명은 유용하네요. 일단 메모리가 부족한 상태인것 부터가 문제겠지만, 막상 저런 문제가 발생했을 때 현상만 가지고는 메모리 부족하고 연결지어 생각하기 어려울테니 알고 있으면 도움이 되겠네요.
 그나저나 스핀카운트를 높게 잡는다는 말은 계속 스핀을 돌겠다는 말 아닌가... 예외 억제하겠다는 이유만으로 무조건 스핀락만 써야한다는 건 좀 아닌거 같은데요. 의도적으로 스레드를 대기모드로 만들고 싶을 때도 있을 테니까요. 일단 임계영역을 생성하고 나서 SetCriticalSectionSpinCount(...)로 스핀 수를 낮추거나 없애면 되려나?

글 작성자분 스크랩 허락해 주셔서 감사합니다 :)


◎ 동기화의 필요성

 다수의 스레드가 하나의 데이터를 동시에 엑세스를 하는 경우는 우리가 스레드 프로그래밍을 하는 동안에는 피할 수 없는 현상이다. 다수의 스레드가 하나의 데이터를 읽기만 한다면야 이 데이터에대한 동기화의 필요성은 필요하지 않을 것이다. 왜냐하면 읽기만 한다는건 데이터가 변하지 않는다는것을 말하기때문이다. 그러므로 어느 스레드에서든 해당 데이터의 값은 동일할 것이다. 하지만 다수의 스레드가 하나의 데이터에 대한 읽기와 쓰기의 작업을 동시에 한다면 스레드의 동기화를 사용하지 않는다면 이 데이터에 대한 무결성을 입증할 방법이 존재할 수 있을까? 답은 없다이다. CPU는 한줄의 대입 연산을 실행하는 경우에도 그에 해당하는 여러줄의 기계어 코드를 만들기때문에 원자적 접근이 되지않기 때문이다.

 ◎ 원자적 접근

 스레드 동기화는 원자적 접근을 보장해주는 일이다. 원자접근은 같은 시간에 같은 리소스에 다른 스레드가 접근하지 않도록 하면서 해당 스레드가 리소스에 접근하도록 하는 일일 말한다.

 ◎ Interlocked 계열의 함수

 Interlocked 계열의 함수는 하나의 long형 변수의 값을 변경할때 원자접근을 수행하는 함수를 말한다. Interlocked 계열의 함수는 다음과 같이 존재한다. (현재는 더 존재할 수 있다.)

. LONG InterlockedIncrement (LONG volatile* Addend) : Addend 에 저장되어있는 long형 변수를 1 증가 시킨다. Rv는 증가된 후의 값이다.

. LONG InterlockedDecrement(LONG volatile* Addend) : Addend 에 저장되어있는 long형 변수를 1 감소 시킨다. Rv는 감소된 후의  값이다

. LONG InterlockedExchage(LONG volatile* Target, LONG Value) : Target에 저장되어있는값을 Value값으로 변경한다. Rv는 최초 Target에 저장되어있는 값이다.

.PVOID InterlockedExchangePointer(PVOID volatile* Target, PVOID Value) : Target에 저장되어있는 값을 Value에 저장되었는 값으로 변경한다. Rv는 최초 Target에 저장되어있는 값이 있는 번지를 리턴한다.

.LONG InterlockedExchangeAdd(LONG volatile* Target, LONG Increment) : Target에 저장되어있는값에 Increment값을 더한다. Rv는 최초 Target에 저장되어있는 값이다.

.LONG InterlockedCompareExchange(LONG volatile* Dest, LONG Exchange, LONG Compare) : Dest에 저장되어있는값이 Compare와 동일하면 Dest의 값을 Exchange으로 변경한다. Rv는 최초 Dest에 저장되어있는 값이다.

.PVOID InterlockedCompareExchangePointer(PVOID* pDest, PVOID pExch, PVOID pCompare) : pDdest에 저장되어있는 값이 pCompare와 동일하면 pDest의값은 pExch값으로 변경된다. Rv는 최초 pDest의 값이다.

 ◎ CRITICAL_SECTION

 CRITICAL_SECTION 은 윈도우에서 제공하는 동기화 객체 중의 하나로서 동기화 객체 중 유일하게 유저모드에서 실행된다.(커널모드로의 전환이 일어나면 CPU사이클을 많이 잡아먹는다. 한마디로 느리다는것이다.)  

  CRITICAL_SECTION의 사용법은 다음과 같다.

1.  CRITICAL_SECTION 구조체를 초기화 한다.

void InitializeCriticalSection(LPCRITICAL_SECTION pCs);

사용할 CRITICAL_SECTION 객체의 주소를 넘겨준다.

2. 임계영역에 진입한다.

VOID EnterCriticalSection(LPCRITICAL_SECTION pCs);

3. 해당작업을 한다. (원자성을 보장한다.)

4. 임계영역을 탈출한다

void LeaveCriticalSection(LPCRITICAL_SECTION pCs);

 ※ 인자로 들어가는 CRITICAL_SECTION 구조체의 주소가 동일해야한다.

 ※ 3번에서 원자성을 보장한다는 얘기는 해당작업을 하는 부분에서 사용하는 공통된 데이터에 접근 하는 모든 스레드에서 EnterCriticalSection와 LeaveCriticalSection를 사용해야 하며, 인자로 넘어가는 CRITICAL_SECTION 의 구조체의 주소는 모두 동일해야 한다.

위의 순서에서 사용된 함수들에 대한 설명은 다음과 같다.

void InitializeCriticalSection(LPCRITICAL_SECTION pCs)

 : CRITICAL_SECTION 구조체를 초기화한다. 이 함수는 단지 멤버변수를 설정하기 때문에 실패하지 않는다. 이 함수는 EnterCriticalSection을 호출하기전에 반드시 호출되어야한다. 초기화되지않은 CRITICAL_SECTION 에 진입을 시도할시 결과는 정의되지 않는다고 SDK문서에 명시되어있다.

※ 이 함수가 실패는 하지 않지만 예외상황을 발생시킬수있다. 위 함수는 디버깅 정보가 있는 메모리 블럭을 할당하기 때문에 메모리 블럭 할당에 실패하였을 경우 STATUS_NO_MEMORY 예외를 던진다. SEH를 이용하여 예외를 Catch할수있다.

.void DeleteCriticalSection(LPCRITICAL_SECTION pCs)

 : DeleteCriticalSection은 구조체 내의 멤버 변수를 재설정한다(제거). 어떤 스레드가 여전히 이 구조체를 사용하고있다면 자연히 임계영역은 삭제할 수 없다. SDK 문서에서는 이렇게 할때 결과는 정의되지 않는다고 나와있다.

.VOID EnterCriticalSection(LPCRITICAL_SECTION pCs)

 : 임계영역에 진입을 시도한다.

 ※ 한 CRITICAL_SECTION 에 대해 두번의 EnterCriticalSection은 시도할경우 최초 진입 성공시 두번째 EnterCriticalSection시에는 CRITICAL_SECTION의 접근 스레드를 표시하는 변수를 업데이트하고 바로 리턴한다. (Enter가 두번이면 Leave도 두번호출해야한다.)

BOOL TryEnterCriticalSection(LPCRITICAL_SECTION pCs)

 : 임계영역에 진입을 시도한다. 이 함수는 이함수를 호출하는 스레드가 절대 대기상태로 진입을 하지 않는다. 이 함수가 나타내는 Rv값은 다른 스레드에서 해당 임계영역에 진입을 하고있는 상태면 FALSE, 아니면 TRUE이다. 이 함수는 해당 임계영역에 진입이 가능하면 진입을 시도하고 아닐경우 바로 리턴하여(FALSE) 다른 일을 한다.

※ Windows 2000 이상에서만 지원한다.

.void LeaveCriticalSection(LPCRITICAL_SECTION pCs)

 : 이 함수는 호출한 CRITICAL_SECTION에 접근하고있는 스레드의 개수를 하나 감소시킨다. 접근스레드의 개수가 1이상이면 감소 후 바로 리턴하고.개수가 0개이면EnterCriticalSection을 호출한 다른 스레드가 대기 상태에 있는지 검사 후 존재하면 해당 스레드에게 맞게 CRITICAL_SECTION 구조체를 업데이트하고 바로 해당스레드를 스케쥴한다.(여러개가 존재할경우 공정하게 선택한단다.-_-;;) 대기하는 스레드가 없을 경우 멤버함수를 업데이트하여 접근하는 스레드가 없음을 나타낸다.

 ◎ 스핀록

 스레드가 EnterCriticalSection을 호출했을 시 해당 임계영역을 다른 스레드가 소유하고있으면 호출한 스레드는 바로 대기모드로 전환한다. 이는 유저모드에서 커널모드로의 전환을 의미한다.(CPU사이클을 많이 소모한다.) 이 전환은 소모가 매우 크다. 그래서 MS는 이 비용을 줄이기 위해 임계 영역안에 스핀록을 만들었다. EnterCriticalSection을 호출했을시 EnterCriticalSection은 리소스를 몇번 동안 요청을 시도하기위해 시핀록을 사용해 루프를 돈다. 오직 모든 시도가 실패하게 될때 스레드는 커널모드로(대기모드) 전환된다.

스핀록을 사용하기위해서는 다음과 같은 함수를 사용한다.

BOOL InitializeCriticalSectionAndSpinCount(LPCRITICAL_SECTION pCs, DWORD dwSpinCount);

: InitliazeCriticalSection을 사용하지 말고 위 함수를 사용하여 CRITICAL_SECTION을 초기화 해야한다. dwSpincount는 리소스 재요청 횟수이다. 위 횟수는 0~ 0x00FFFFFF 사이의 어떤값도 될수 있다.InitliazeCriticalSection 은 항상 성공하며 가끔 예외(?)를 던지지만, 이 함수는 메모리 할당에 실패하면  FALSE를 리턴한다. 훨씬 좋다. 사용하자.

 ※ 프로세서가 하나인 머신에서는 dwSpinCount무시되고 항상 0 으로 설정된다.

DWORD SetCriticalSectionSpinCount(LPCRITICAL_SECTION pCs, DWORD dwSpinCount)

 : 위 함수는 해당 CRITICAL_SECTION객체의 스핀횟수를 변경한다.

 ※ 역시 프로세서가 하나인 머신에서는 위의  dwSpinCount값은 무시되고 0으로 설정된다.

※ 임계영역에서 스핀록을 사용하면 손해보지는 않는다. 지원을 하면 항상 사용하자.

 ◎ 임계영역 & 에러 핸들링

내부적으로 임계 영역은 두개 이상의 스레드가 동시에 임계 영역을 가지고 경쟁할 경우 이벤트 커널 오브젝트를 사용한다. 이런 경쟁이 드물면 시스템은 이벤트 커널 오브젝트를 생성하지 않는다. 메모리가 적은 상황에서 임계 영역에대한 경쟁 상황이 발생할 수 있고 시스템은 요청된 이벤트 커널 오브젝트의 생성에 실패하게 되면  EnterCriticalSection함수는 EXCEPTION_INVALID_HANDLE 예외를 발생한다. 이런 예외에 대한 처리는 대부분 하지 않기 때문에 치명적인 상황이 발생할 수 있다.

 위의 상황을 방지하기 위해서는 SEH로 예외를 핸들링하는방법과(비추이다.)

InitializeCriticalSectionAndSpinCount을 사용해서 임계영역을 생성하는것이다. (dwSpincount를 높게 설정해서) dwSpinCount가 높게 설정되면 이 함수는 이벤트 커널 오브젝트를 생성하여 이것을 임계영역과 연결한다. 만약 높은 비트로 설정이 되었는데 이벤트 커널 오브젝트가 생성되지 않으면 이 함수는 FALSE를 리턴한다. 성공적으로 이벤트 커널 오브젝트가 생성되면 EnterCriticalSection은 잘동작하고 절대 예외를 발생하지 않는다.

※ EnterCriticalSection 실행 시 다른 스레드가 해당 임계영역을 소유하고있다면 무기한 대기를 하는것이 아니고 레지스트리에 등록된 시간만큼만 대기하고 하나의 예외를 발생한다.

위치는 HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manger 에 위치한 CriticalSectionTimeout 값이다. 단위는 초이고 기본값으로 2592000초이다. 대략 30일이다.;; (한마디로 무한대기구먼.ㅡ_ㅡ;;)

Posted by leafbird 트랙백 0 : 댓글 1

댓글을 달아 주세요

  1. addr | edit/del | reply Favicon of https://devnote.tistory.com BlogIcon leafbird 2012.01.27 18:46 신고

    위 내용은 Windows Via C/C++ 8장의 내용 정리인데, 가장 아래에 에러 핸들링에 대한 동작은 WinXP 이전 OS들의 동작방식이다. 자세한 내용은 책을 참고.
    곧 8장 내용을 직접 정리한 자료를 다시 올릴 테니 혼동 없으시기 바랍니다.

ABA Problem

2011. 11. 17. 16:21 from 프로그래밍 팁/Thread
 이틀 전에 멀티스레드 접근에 안전한 스택 - Interlocked Singly Linked List을 소개하는 글을 적었다. 관련된 자료들을 검색해보니 자연스럽게 그 다음 스텝인 ABA Problem을 만나게 되어 정리해 둔다.

http://en.wikipedia.org/wiki/ABA_problem

예전에 아꿈사 스터디에서 윤석윤 님( http://seedyoon.tistory.com/ )이 말씀해주신 것을 들어서 알고 있기는 했지만, 그땐 무슨 문제인지 자세하게 몰랐다. ABA라는게 메모리의 할당된 형태를 묘사하는 줄로 이해해서, 서로 다른 크기의 메모리 할당을 번갈아가며 했을때 생기는 문제인가 했는데, ABA는 스레드 접근 순서를 묘사한 것. lock-free 알고리즘을 다룰 때 필수 요소인 CAS(Compare And Swap)연산이 공유 객체의 변경을 감지하지 못하게 되는 현상을 말한다.

왜 감지를 못하게 되는가. 일반적으로 InterlockedCompareExchange(...)를 이용해서 변수의 값을 수정할 때는 거의 걱정할 필요가 없다. 하지만 Interlocked Singly Linked List처럼 자료구조를 사용하게 될 때 주의가 필요하다. 자료구조에 담기는 데이터의 포인터가 시스템에 의해서 재 사용될 수 있기 때문. 스레드 A가 자료구조에 담긴 값(메모리 주소)만을 가지고 값이 변경 되었는지 여부를 감지하고자 하는 순간에, 다른 스레드 B에서 데이터의 push와 pop을 연달아 수행하다 우연히 지워진 메모리 주소가 재사용된다면, 실제로는 값이 변경되었음에도 불구하고 스레드 A는 값의 변경을 알아채지 못하게 된다.

그래서 윈도우에서 사용되는 Interlocked Singly Linked List를 쓸 때에는 주의가 필요하다. 뿐만 아니라 lock-free 방식으로 동작하는 다른 자료구조의 컨테이너를 제작한다 하더라도 이 ABA Problem의 발생에 대해 주의를 기울여야 한다. 

정정. 윈도우xp부터 제공되는 lockfree stack인 Interlocked Singly Linked List는 ABA문제에 대한 걱정 없이 사용 가능하다. 내부적으로 별도의 tag를 달아서 이를 해결하고 있다. 


역시나 구글링한 결과들을 몇 개 나열해 본다.


 



Posted by leafbird 트랙백 0 : 댓글 2

댓글을 달아 주세요

  1. addr | edit/del | reply Favicon of https://devnote.tistory.com BlogIcon leafbird 2012.01.27 18:42 신고

    a classic parallel problem known as ABA, where thread1 observes x == A, thread2 does x=B and then x=A and thread1 then observes x == A.
    아마존에서 책 목차인줄 알고 읽고 보니 어느 독자의 리뷰였던(...) 글 중에서.
    http://www.amazon.com/Art-Multiprocessor-Programming-Maurice-Herlihy/dp/0123705916

  2. addr | edit/del | reply Favicon of https://devnote.tistory.com BlogIcon leafbird 2012.01.27 18:48 신고

    ABA 문제의 해결을 위해서는 데이터 이외의 스탬프 정보가 필요하다는 간략한 메모글.
    http://blog.daum.net/nicegoori/8896304

윈도우 API중에 Interlocked 계열 API들은 멀티스레드 프로그래밍을 할 때 유용하게 쓰인다. 여러 종류의 함수들이 있지만 대부분은 primitive type의 데이터의 값을 증가, 감소, 비교 변경하는 기능을 제공하지만 조금 특이하게 자료구조를 다루는 함수들이 있다.

Interlocked Singly Linked List라는 이름의 이 자료구조는 윈도우에서 기본으로 제공되는, 멀티 스레드의 접근에 대해 단일 액세스를 보장해주는 stack이다. 이름만 봐서는 FIFO로 동작할 것 같지만 실제 동작은 Push/Pop이 같은 곳에서 일어나는 FILO 방식이니 주의해야 한다.

이 스레드 세이프한 스택은 일반적으로 널리 쓰이는 다른 Interlocked 계열 함수들과 연관 지어 알아두는 것보다는 연결 리스트를 다루는 함수들과 연관 짓는 것이 더 맞을지도 모른다. 윈도우는 Singly Linked List와 Doubly Linked List를 기본 지원하는데, 이 중에서 단일 스레드 접근을 보장해주는 단일 연결 리스트 기능이 Interlocked Singly Linked List이다. 기본 연결리스트 기능들은 윈도우 2000부터 지원했고, Interlocked 방식은 윈도우 XP와 Server 2003 버전 이상부터 지원된다. (http://msdn.microsoft.com/en-us/library/ff563802.aspx )

API들의 소개와 샘플 코드를 정리하려고 했는데, 검색해보니 자료가 참 많다. 그리고 이미 매우 잘 정리된 포스팅 들이 많아 무의미 할 것 같다. 구글링한 링크나 몇 개 정리해본다.

그나저나 이런 윈도우 관련 디테일한 지식들을 잘 정리해 놓은 책은 없는지 한 번 살펴봐야겠다. 그냥 작업하다 듣게 되거나 보게 되어서 알게 되는 것 말고 한 방에 싹 공부해두면 좋겠는데. 책을 사두면 레퍼런스로 쓰기도 좋잖아. 혹시 이 글 보이는 분 중에 추천해 주실 책이 있으면 지체 없이 추천 바랍니다 :)

 

Posted by leafbird 트랙백 1 : 댓글 1

댓글을 달아 주세요

  1. addr | edit/del | reply Favicon of https://devnote.tistory.com BlogIcon leafbird 2012.01.27 18:24 신고

    Windows Via C/C++ 책 스터디중.
    윈도우 API에 대한 디테일한 설명이 비교적 잘 나와있는 편이지만
    해당 포스팅에서 다루고 있는 Interlocked Singly Linked List에 대해서는 자세히 나와 있지는 않다.
    딱 네 줄 나옴(293p). API 소개하는 책이니 만큼 ABA 문제 등에 대한 설명은 없다.

참고 : Windows via C/C++ 책에 다른 동기화 방식들과 함께 친절히 비교/설명되어 있는 듯.

http://msdn.microsoft.com/en-us/library/aa904937(v=VS.85).aspx

몇 달 전에 온라인 게임 서버 개발자 카페 오프라인 모임에 갔다가 알게 된 사실.

이 날 세미나 하셨던 분이 다른 주제로 API들을 죽 열거하는 시간이 있었는데, 윈도우 2008부터 새롭게 지원되는 read/write 잠금이 있다는 걸 처음 알았다. +_+


그 뒤로도 스터디 멤버들하고 개발 이야기를 하다보면 간간히 이녀석들 이야기가 들리길래 벌써 사용해보는 프로젝트들이 몇 군데 있나보구나... 하고 있다가, 임시로 나도 필요한 곳이 생겨서 이참에 한 번 사용해 보았음.

포스팅 맨 위에 링크된 문서의 API들이 전부인 아주 간단한 모듈이다. 이름도 'SLIM' Reader/Writer lock.
SRWLOCK 구조체에는 void형 포인터 하나만 들어있다. 많은 기능을 넣어둔 것이 아니라 가볍고 심플한 기능만을 넣어 두었다. 때문에 사용할 때 몇 가지 주의할 사항이 있는데,

An SRW lock is the size of a pointer. The advantage is that it is fast to update the lock state. The disadvantage is that very little state information can be stored, so SRW locks cannot be acquired recursively. In addition, a thread that owns an SRW lock in shared mode cannot upgrade its ownership of the lock to exclusive mode.
무지 빠르고 공간 소비가 적은 것은 슬림 설계의 장점이고,
재귀적으로 lock을 호출할 수 없다는 것(한 번 lock을 얻은 스레드가 다시 lock을 얻으려고 하면 안됨)과 reader lock을 얻은 스레드가 writer lock으로 바로 갈아탈 수 없다는 점은 주의사항이다.

Try... 함수들을 사용하고 적절히 대기처리 해주면 스핀락으로 사용할 수도 있다.
문서의 shared mode가 reader lock이고, exclusive mode가 writer lock이다.

굳.
Posted by leafbird 트랙백 0 : 댓글 0

댓글을 달아 주세요

출처 : http://kukuta.tistory.com/35

Read/Write Lock(이하 rwlock)의 기본적인 생각은 아래와 같이 간단하다.

* Read 작업은 값을 변경하지 않으니 몇개가 붙어도 동기화에 문제가 생기지 않는다.
* Write 작업은 값을 변경 할 수도 있으니, 하나의 크리티컬 섹션에 하나만 붙어야 한다.
* Read 작업은 끼리는 여러개가 하나의 크리티컬 섹션에 접근 가능하나, Read하는 것을 도중에 바꾸면 안된다.

위의 세 가지 사항 정도만 제외 한다면 rwlock은 mutex랑 비슷하다. 개념도 비슷하고, 사용법도 비슷하고, 하는
일도 비슷하다.

rwlock의 장점은 read 작업에 있어서 여러개의 쓰레드가 서로 블록킹 하는 일이 없어 괜한 오버헤드를 가지고 오
지 않고, 단점은
그 특성상 read작업이 많다면 거기에 눌려 write 오퍼레이션은 뒤로 밀리기 마련이다.

write lock을 걸려고 하는 쓰레드가 자원을 못 받아 말라 죽는
시나리오를 만들어 보자.

1) 1번 쓰레드가 A라는 공유 자원을 참조 하려고 한다.
*
아직 아무런 lock이 걸려 있지 않으므로 커널은 lock을 허락한다.
2) 2번 쓰레드가 A라는 공유 자원을 변경 하려고 한다.
* 변경이라는 것은 wrtie lock을 걸어야 하는데 아직 read 작업이 있으므로 write를 잠시 멈춘다.
하지만 그 사이에 다른 read 작업이 또 들어 온다. read 작업은 공유가 되므로 커널은 뒤 늦게 온 read 작업을
크리티컬섹션 안으로 넣어 버린다. 이런 현상의 반복으로 write를 요청하는 thread는 굶어 죽는 경우가 발생
해 버릴수 있다.(현실적으로는 이런 경우를 대비하여 만들었 겠지만, 그래도 rwlockmodify작업보
다는 read가 더 많은 곳에 사용하는 것이 가장 이상적이다
.)



스레드 스위칭을 줄이기 위한 기법들이 이렇게나 간절한(?) 거였구나.

예전엔 필요없다고 다 그냥 지나치던 것들인데.

새삼 멀티스레딩이 다시 보이는 요즘이다.

이젠 CRITICAL_SECTION 다시 못쓰겠다 ㅎ

Posted by leafbird 트랙백 0 : 댓글 0

댓글을 달아 주세요

Sleep( 0 );

2011. 4. 4. 16:11 from 프로그래밍 팁/Thread

Sleep(..) 함수에 넣을 인자값으로 0을 넣어주면, 해당 스레드는 남은 시간자원을 대기중인 다른 스레드에게 양보하게 된다. 만약 이 때 대기중인 다른 스레드가 없다면 이 함수는 즉시 리턴되고, 현재 스레드는 실행을 계속하게 된다. XP나 2000 버전에서는 자신과 동일한 우선순위(priority)를 가진 스레드에 대해서만 양도 가능한지를 확인하는 점이 다름. Win2003 이후부터는 우선순위와는 무관하게 동작한다.

출처 : http://msdn.microsoft.com/en-us/library/ms686298(VS.85).aspx

이제 스레드의 컨텍스트 스위칭에 드는 비용에도 관심을 가져야 할 때가 되었다. 예전엔 가장 단순하게, 가장 쉽게 구조를 잡고 코드를 짜는 것이 미덕이었으나 이젠 그러면 큰일남…
이전엔 최대한 스레드간의 간섭이 적도록 설계해두고 두어 군데 교착지점만 신경 써주면 멀티스레드 프로그래밍 상황 종료였다. 크리티컬 섹션 걸어주는 것 말고 내가 스레드에 대해서 그다지 아는게 많이 없었다는 사실이 새삼 느껴짐…

공부할 건 아주 지천에 널렸구나. 아이 씐나 @0@)/

아주 기초적인 내용이지만… InterlockedXXX 계열들의 함수에 대해서도 정리를 한 번 해야겠다. 리스트 자료구조를 지원하는 함수도 있고.. 이쪽도 내가 모르는 게 많쿤화!

Posted by leafbird 트랙백 0 : 댓글 0

댓글을 달아 주세요

void ThreadEntry()
{
    while( true )
    {
        throw "뻗어라";
    }
}

...

boost::thread thr( &ThreadEntry );

::system( "pause" );


위에 코드 테스트로 돌려보면
프로세스가 죽는게 아니고 boost::thread로 만든 스레드만 조용히 사라진다.
처리하고 싶다면 스레드 엔트리 함수에 try-catch를 걸고 직접 raise()를 호출할 것.

void ThreadEntry() try
{
    while( true )
    {
        throw "뻗어라";
    }
}
catch( std::exception& e )
{
    // ,,,,
    raise( 0 );
}
catch( ... )
{
    // ....
    raise( 0 );
}


...

boost::thread thr( &ThreadEntry );

::system( "pause" );



boost::thread를 꽤 예전부터 써왔는데 이거는 오늘 처음 알았다...;;



 Exceptions

Boost.Threads destructors never throw exceptions. Unless otherwise specified, other Boost.Threads functions that do not have an exception-specification may throw implementation-defined exceptions.

In particular, Boost.Threads reports failure to allocate storage by throwing an exception of type std::bad_alloc or a class derived from std::bad_alloc, failure to obtain thread resources other than memory by throwing an exception of type boost::thread_resource_error, and certain lock related failures by throwing an exception of type boost::lock_error.


void
work()
    {
    boost::exception_ptr error;
    boost::thread t( boost::bind(worker_thread,boost::ref(error)) );
    t.join();
    if( error )
        boost::rethrow_exception(error);
    }


reference

 

Posted by leafbird 트랙백 0 : 댓글 1

댓글을 달아 주세요

  1. addr | edit/del | reply Favicon of https://devnote.tistory.com BlogIcon leafbird 2010.08.30 16:45 신고

    예시에서 처리한 방법은 function try block이라는 새로운 구문.
    vs2005부터 지원한다.

__declspec(thread)라는 재미있는 기법을 알았습니다.
예전 작업들 중에 __declspec(thread)를 알았더면 훨씬 더 간편했을 법한게 몇개 떠오르네요.
아래에 간략한 설명을 네이버 검색에서 퍼왔습니다.

원문 출처 : http://blog.naver.com/kimsk99/50002386480

TLS(Thread Local Storage)를 사용하기 위한 Visual C++의 방법은 __declspec(thread)를 이용하는 것과 TLS조작함수를 사용해서 직접 조작하는 것의 두가지 방법이 있다.

 

TLS 조작함수를 쓰게되면 dynamic하게 TLS에 공간을 할당하고 삭제할 수 있다.

TlsAlloc()에 의해서 DWORD크기의 공간이 추가적으로 TLS에 할당되고, 할당된 공간을 가리키는 index가 리턴된다.

이 TLS공간은 TlsGetValue(), TlsSetValue()에 의해서 조회/변경될 수 있다.

 

__declspec(thread)를 사용해서 변수를 할당하는 것은 프로그램 컴파일 때 TLS의 크기를 설정하는 것이다.

즉 모든 thread에서 가지는 기본적인 TLS를 컴파일 때 계산해서 만들어 내고 그 정보가 실행파일에 담기는 것이다.

__declspec(thread)로 만들어진 변수를 참조하게 되면 어셈블리로 다음과 비슷한 코드가 만들어진다.

{{  move eax,dword_ptr [__tls_index(452358h)]   }}

__declspec(thread)의 변수에 대한 참조가 __tls_index()라는 형태로 바뀐 것을 볼 수 있다.

이렇게 어셈블리 레벨에서 정적인 TLS에 대한 지원이 되어 있는 것을 볼 수 있다.

이 경우라면 TLS에 대한 참조는 아주 빠른 시간에 이루어질 수 있다.

 

TLS를 사용할 경우 대부분은 정적인 할당이면 충분하다.

특별히 많은양의 TLS를 사용해야 하기 때문이 아니라면 동적인 할당은 불필요해 보인다.

그렇기 때문에 VC에서는 가능하면 __declspec(thread)를 사용하는 것이 효율적인 코드를 사용할 수 있다.

물론 VC가 아니거나 TLS에 대한 어셈블리 레벨 지원이 없는 환경이라면 TLS 조작함수를 직접 사용해야 한다.

TAG Thread, tls
Posted by leafbird 트랙백 0 : 댓글 0

댓글을 달아 주세요

며칠전 shuffle에 관한 작업을 하다 저희팀원(최상만씨 이름 한번 밝히고 ^^)이
stl에 random_shuffle() 이라는 함수로 작업을 하던 중 이었습니다.
근데 이게 희한하게도 내부적으로 rand()를 사용하는데 srand()를 내부적으로 호출하지
않고 명시적으로 해주게 되어있던데, 이상하게도 계속 shuffle 결과 값이 동일하게
나오는 것이었던 것입니다.
근데 shuffle 바로전에 srand()를 해주면 작동도 잘되고...그래서 무작정 crt를 뒤져봤죠.
그랬더니, c-runtime random을 multi-thread에서 돌리면 해당쓰레드마다 seed값을
줘야 되더라구요...
그니깐 multi-thread에서는 srand()를 각 thread 마다 해줘야 정상적으로 작동하더라구요.
다 아시는 거 겠지만, 함 적어봄다..

출처 : http://www.extremendl.net/zero/bbs/zboard.php?id=v2_usefulinfo&no=84

:: 현재 서버에서 사용중인 KThread, KTThread 클래스에서 스레드 생성 초기에 srand를 불러주도록 수정해야 함.
Posted by leafbird 트랙백 0 : 댓글 0

댓글을 달아 주세요