메인 컨텐츠로 가기

developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관 보기.

developerWorks에 처음 로그인하면 developerWorks프로파일이 생성됩니다.귀하의 프로파일에서 동의하신 내용이 공개되지만 이 사항은 언제든지 변경 가능합니다. 귀하의 성명(숨김으로 체크되어 있어도 표시됩니다)과 디스플레이 이름은 게시한 컨텐츠나 사이트 엑세스시 표시됩니다.

모든 정보가 안전하게 전송되었습니다.

  • 닫기 [x]

처음 developerWorks에 로그인할 때 프로파일이 작성되므로, 이를 위해 디스플레이 이름을 선택해야 합니다. 선택하신 디스플레이 이름은 developerWorks에 게시한 컨텐츠에 표시됩니다.

3글자 이상 31글자 이하의 길이로 사용 가능합니다. dW커뮤니티 내에서는 보안상 이메일주소를 제외한 다른 이름을 지정하셔야 합니다.

developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관 보기.

모든 정보가 안전하게 전송되었습니다.

  • 닫기 [x]

Common Threads: POSIX 쓰레드, Part 2

뮤텍스(Mutex) 사용

Daniel Robbins, CEO , Gentoo Technologies, Inc
Daniel Robbins는 Gentoo Technologies, Inc.의 회장/CEO이고, Gentoo Project의 핵심 설계자이며, Caldera OpenLinux Unleashed, SuSE Linux Unleashed, Samba Unleashed의 저자이다

요약:  POSIX 쓰레드는 코드의 반응과 성능을 향상시키는 좋은 방법이다. 세 부분으로 이루어진 시리즈의 두 번째인 이 글에서 Daniel Robbins는 뮤텍스라는 작은 것을 사용하여 쓰레디드 코드(threaded code)에서 공유 데이터 구조의 무결성을 보호하는 방법을 알려준다.

원문 게재일:  2000 년 8 월 01 일
난이도:  초급
페이지뷰:  2063 회
의견:  


뮤텍스

이전 글에서는 예외적이며 예상되지 않은 작업을 수행하는 쓰레디드 코드에 대해 이야기했다. 두 개의 쓰레드가 각각 전역 변수를 20번 증분했다. 이 변수의 예상되는 최종 값은 40이었지만, 실제 최종 값은 21이었다. 어떻게 된 것인가? 문제의 원인은 쓰레드가 다른 쓰레드에 의하여 수행된 증분를 반복적으로 "취소했기" 때문이었다. 이 문제를 해결하기 위하여 뮤텍스를 사용하는 수정된 코드를 살펴보자.:


thread3.c

#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>

int myglobal;
pthread_mutex_t mymutex=PTHREAD_MUTEX_INITIALIZER;

void *thread_function(void *arg) {
  int i,j;
  for ( i=0; i<20; i++ ) {
    pthread_mutex_lock(&mymutex);
    j=myglobal;
    j=j+1;
    printf(".");
    fflush(stdout);
   sleep(1);
    myglobal=j;
    pthread_mutex_unlock(&mymutex);
  }
  return NULL;
}

int main(void) {

  pthread_t mythread;
  int i;

  if ( pthread_create( &mythread, NULL, thread_function, NULL) ) {
    printf("error creating thread.");
    abort();
  }

  for ( i=0; i<20; i++) {
    pthread_mutex_lock(&mymutex);
    myglobal=myglobal+1;
    pthread_mutex_unlock(&mymutex);
    printf("o");
    fflush(stdout);
    sleep(1);
  }

  if ( pthread_join ( mythread, NULL ) ) {
    printf("error joining thread.");
    abort();
  }

  printf("\nmyglobal equals %d\n",myglobal);

  exit(0);

}


이해하기

이 코드를 이전 글에 있는 버전과 비교해 보면, pthread_mutex_lock() 및 pthread_mutex_unlock()이 추가된 것을 알 수 있다. 이러한 호출로써 쓰레디드 프로그램에서 필수적인 기능이 수행된다. 그 함수들은 mutual exclusion(상호 배제)(mutex라는 명칭 유래)의 수단을 제공하는 것이다. 두 개의 쓰레드가 잠겨진(locked) 뮤텍스를 동시에 소유할 수는 없다.

뮤텍스의 원리는 다음과 같다. 쓰레드 "a"가 뮤텍스를 잠그려고 시도할 때, 쓰레드 "b"가 이미 잠궈놓은 동일한 뮤텍스를 소유하고 있으면, 쓰레드 "a"는 휴면(sleep) 상태로 간다. 쓰레드 "b"가 뮤텍스를 릴리스 하자마자(pthread_mutex_unlock() 호출을 통해), 쓰레드 "a"는 해당 뮤텍스를 잠글 수 있을 것이다(즉, pthread_mutex_lock()에서 리턴). 마찬가지로, 쓰레드 "a"가 뮤텍스를 소유하는 동안 쓰레드 "c"가 뮤텍스를 잠그려고 하면, 쓰레드 "c" 또한 일시적으로 휴면 상태가 될 것이다. 이미 잠긴 뮤텍스에 pthread_mutex_lock()을 호출하여 휴면 상태가 되는 모든 쓰레드는 해당 뮤텍스에 액세스하려고 대기할 것이다.

pthread_mutex_lock()와 pthread_mutex_unlock()은 데이터 구조를 보호하기 위하여 사용된다. 즉, 단지 하나의 쓰레드 만이 어떤 데이터 구조를 잠그거나 잠금 해제하여 그 데이터 구조에 액세스할 수 있는 것은 확실하다. 짐작되는 바와 같이, POSIX 쓰레드 라이브러리는 어떤 쓰레드가 잠겨져 있지 않은 뮤텍스를 잠그려고 시도하면 POSIX 쓰레드 라이브러리는 그 쓰레드를 휴면 상태에 두지 않고 잠금을 허용할 것이다.

이 그림에서 뮤텍스를 잠그고 있는 쓰레드는 다른 쓰레드가 동시에 간섭할 지에 대한 염려없이 복잡한 데이터 구조에 액세스하게 된다. 데이터 구조는 뮤텍스가 잠금 해제될 때까지 사실상 "동결되어" 있다. 이것은 마치 pthread_mutex_lock() 및 pthread_mutex_unlock()를 호출하는 것이 "공사 중"에 있다는 신호와 같은데, 이 신호는 수정하거나 읽고 있는 공유 데이터의 특정 부분을 둘러쌓고 있다. 이러한 호출은 다른 쓰레드에게 휴면 상태에서 자기 차례를 기다리라는 경고의 역할을 한다. 물론 이것은 pthread_mutex_lock() 및 pthread_mutex_unlock()를 호출함으로써 특정 데이터에 대한 모든 읽기 및 쓰기 작업이 보호되어 있을 때에만 가능하다.


왜 뮤텍스인가?

재미있게 들리지만, 쓰레드를 왜 꼭 휴면 상태에 두어야 하는가? 결국, 쓰레드의 주요한 장점이란 많은 경우에 독립적으로 동시에 작동하는 능력이 아닌가? 맞는 말이다. 하지만, 모든 중요한 쓰레드 프로그램에는 적어도 약간의 뮤텍스를 사용하여야 할 것이다. 예제 프로그램을 참고하여 그 이유를 알아보자.

thread_function()을 살펴보면, 뮤텍스가 루프의 시작에서 잠겨지고 루프의 마지막 끝에서 잠금 해제되는 것을 볼 수 있다. 이 예제 프로그램에서, mymutex는 변수 myglobal의 값을 보호하기 위하여 사용된다. thread_function()을 주의 깊게 살펴보면, 증분 코드에서 myglobal을 로컬 변수에 복사하고, 로컬 변수를 증분하고, 1초 동안 휴면 상태가 되고 나서야 로컬 변수를 다시 myglobal에 복사함을 알 수 있을 것이다. 뮤텍스가 없다면, 그리고 thread_function()이 1초간 휴면하는 동안 메인 쓰레드가 myglobal을 증분 한다면, thread_function()은 깨어났을 때, 증분 된 값을 덮어 쓸 것이다. 뮤텍스를 사용하면 확실히 이러한 일이 일어나지 않는다. (여러분이 의아해 할 경우를 대비해서, 흠집 있는 결과를 유발하려고 1초간의 지연을 더한 것이다. thread_funtion()이 로컬 변수의 값을 다시 myglobal에 복사하기 전에, 1초 동안 휴면 상태로 가야 할 진정한 이유란 없는 것이다.) 뮤텍스를 사용하는 프로그램은 원하는 결과를 산출한다 :



$ ./thread3
o..o..o.o..o..o.o.o.o.o..o..o..o.ooooooo
myglobal equals 40

아주 중요한 개념을 더 깊이 고찰하기 위하여, 이 프로그램의 증분 코드를 살펴보자 :



thread_function() increment code: 
   j=myglobal;
    j=j+1;
    printf(".");
    fflush(stdout);
    sleep(1);
    myglobal=j;

main thread increment code:
    myglobal=myglobal+1;

이 코드가 단일 쓰레드 프로그램에 있다면, thread_function()코드가 완전히 실행될 것이다. 그 다음에 메인 쓰레드 코드가(또는 순서에 관계없이) 뒤를 따른다. 뮤텍스가 없는 쓰레디드 프로그램에서, 이 코드는 결국 다음의 순서로 실행을 끝낼 수 있다. (종종 sleep() 호출로서 끝낼 것이다) :



    thread_function() thread	main thread

    j=myglobal;
    j=j+1;
    printf(".");
    fflush(stdout);
    sleep(1);		        myglobal=myglobal+1;
    myglobal=j;

코드가 이러한 특정 순서로 실행될 때, myglobal에 대한 메인 쓰레드의 수정은 덮어 씌어 진다. 그러면 프로그램의 마지막에서 틀린 값으로 끝나게 된다. 만약 포인터를 다루는 중 이었다면, 아마 segfault(segmentation fault)로 끝날 것이다. thread_function() 쓰레드는 모든 명령어를 순서대로 실행한다는 것에 주의해야 한다. thread_function()은 잘못된 순서로 작업을 수행하지는 않는다. 문제는 동일한 데이터 구조에 동시에 다른 수정 작업을 효과적으로 수행하는 또 다른 쓰레드가 있다는 것이다.


쓰레드 살펴보기 1

뮤텍스가 사용될 곳을 파악하는 방법을 설명하기 전에, 쓰레드의 내부 작동에 대하여 잠시 설명하겠다. 다음은 첫 번째 예제이다 :

"a", "b", "c" 3개의 새로운 쓰레드를 생성하는 메인 쓰레드가 있다고 가정하자. 쓰레드 "a"가 맨 처음 생성되고, 쓰레드 "b"가 두 번째, 쓰레드 "c"가 마지막으로 생성된다고 가정하자.



	pthread_create( &thread_a, NULL, thread_function, NULL);
	pthread_create( &thread_b, NULL, thread_function, NULL);
	pthread_create( &thread_c, NULL, thread_function, NULL);

첫 번째 pthread_create()가 호출된 후, 쓰레드 "a"가 현재 존재하거나 쓰레드 "a"가 이미 완료되어 지금은 중단된 상태라고 가정할 수 있다. 두 번째 pthread_create()가 호출된 후, 메인 쓰레드와 쓰레드 "b" 양쪽 모두에서 쓰레드 "a"가 존재한다(또는 중단되었다)는 것을 가정할 수 있다.

하지만, 두 번째 create() 호출이 리턴한 직후에, 메인 쓰레드에서는 실제로 어느(a 또는 b) 쓰레드가 먼저 시작될지 가정할 수 없다. a 와 b 양쪽 쓰레드가 모두 존재하더라도, CPU 타임 슬라이스 제공은 커널과 쓰레드 라이브러리에 달려있다. 그리고 누가 먼저 실행하느냐에 관한 엄격한 규칙도 없다. 쓰레드 "a"가 "b"보다 앞서 실행을 시작될 가능성이 크지만 확실하지는 않다. 이것은 멀티 프로세서 기계(multi-processor machine)에서 두드러지게 나타나는 사실이다. 실제로 쓰레드 "a"가 "b"보다 먼저 실행된다고 가정할 수 있는 코드를 작성한다면, 99%의 시간 동안 작동하는 프로그램이 될 것이다. 더 나쁜 경우라면, 해당 기계에서는 100%의 작동하지만 클라이언트의 쿼드 프로세서(quad-processor) 서버에서는 0%의 시간 동안 작동하는 프로그램이 될 것이다.

이 예제에서 알 수 있는 또 하나는 쓰레드 라이브러리가 각각의 개별 쓰레드에 대하여 코드 실행 순서를 유지한다는 것이다. 다시 말하면, 이 3개의 pthread_create()의 호출은 발생 순서대로 실행될 것이다. 메인 쓰레드의 관점에서 보면, 모든 코드는 순서대로 실행되고 있다. 때때로 쓰레디드 프로그램의 일부분을 최적화하는데 이것이 이용될 수 있다. 예를 들면, 위의 예제에서 쓰레드 "c"는 쓰레드 "a"와 "b"가 실행 중이거나 또는 종료되었다고 가정할 수 있다. 쓰레드 "a"와 "b"가 아직 생성되지 않았을 가능성에 대하여 걱정할 필요는 없다. 쓰레디드 프로그램을 최적화하는데 이러한 로직을 사용할 수 있다.


쓰레드 살펴보기 2

이제 가정할 수 있는 또 다른 예제를 보자. 다음의 코드를 실행하는 많은 수의 쓰레드를 가지고 있다고 가정하자 :



	myglobal=myglobal+1;

증분 전후에 일일이 뮤텍스를 잠그고 잠금 해제할 필요가 있을까? 어떤 사람은 "아니다"라고 할 것이다. 컴파일러는 결국 위의 할당을 단일 기계 명령어로 컴파일할 것이다. 알다시피, 단일 기계 명령어는 중간(mid-stream)에 인터럽트될 수 없다. 하드웨어 인터럽트 조차도 기계 명령어의 원자성을 존중할 것이다. 이러한 경향 때문에, pthread_mutex_lock() 및 pthread_mutex_unlock()를 한꺼번에 호출하고 싶은 유혹이 생길 수 있으나, 그렇게 하지 말아야 한다.

본인이 겁쟁이처럼 보이는가? 그렇지 않다. 첫째, 개인적으로 직접 기계명령어를 확인하지 않는다면, 위의 할당이 단일 기계명령어로 컴파일될 것이라고 가정하지 말아야한다. 증분이 원자적으로 발생한다는 것을 확실히 하기위해 인라인 어셈블리를 삽입했다 할지라도 - 또는 컴파일러가 여러분이 직접 만든 것이라 할지라도 - 문제는 여전히 존재할 것이다.

그 원인은 다음과 같다. 단일 인라인 어세블리 연산 코드(inline assembly opcode)를 사용하면 단일 프로세서 기계에서는 원활한 작동이 이루어질 것이다. 각각의 증분은 원자적으로 발생할 것이며, 원하는 결과를 얻게 될 것이다. 하지만 멀티 프로세서 기계는 다르다. 멀티 CPU 기계에서 상기의 할당을 거의(때때로 정확히) 동시에 실행할 수 있는 개별적인 두 프로세서가 확보될 수 있다. 그리고 이 메모리 수정은 L1에서 L2 캐시로, 그 다음 주기억장치(main memory)로 조금씩 진행되는 것이 필요하다는 것을 기억해야 한다. (SMP 기계는 단지 프로세서만 추가한 것이 아니다; 여기에는 RAM에 대한 액세스를 조정하는 특수 하드웨어가 갖추어져 있다.) 결국, 실제로 어떤 CPU가 주기억장치에 대한 쓰기 경쟁에서 "승리할 지" 알 수 없다. 예측 가능한 코드를 작성하려면, 뮤텍스의 사용을 원하게 될 것이다. 뮤텍스는 "메모리 장벽"을 삽입할 것이며, 이 장벽에 의해 주기억장치에 대한 쓰기가 쓰레드가 뮤텍스를 잠그는 순서로 발생한다는 것이 보장된다.

주기억장치를 32 비트 블록으로 갱신하는 SMP 아키텍처를 생각해보자. 뮤텍스 없이 64비트 정수를 증분하고 있다면, 상위의 4바이트는 하나의 CPU로부터 오고 다른 4바이트는 다른 CPU로부터 올 것이다. 낭패다! 최악의 경우, 서투른 기술이 사용되면 프로그램이 오전 3 시에 주요 클라이언트 시스템에 폭격을 가할 것이다. David R. Butenhof 는 자신의 책, Programming with POSIX Threads(이 글의 마지막 부분에 있는 참고자료 참조)에서 뮤텍스를 사용하지 않고 가능한 순열을 다룬다.


다수의 뮤텍스들

뮤텍스가 너무 많으면, 코드에는 어떤 종류의 동시성(concurrency)도 결여되며 단일 쓰레드 솔루션보다 느리게 실행될 것이다. 뮤텍스가 너무 적으면, 코드에는 괴상하고 황당한 버그가 생길 것이다. 다행스러운 점은 그 중간에서 적당한 수준을 찾을 수 있다는 것이다. 무엇보다도, 뮤텍스는 공유 데이터에 직렬화(serialize)된 접근을 위하여 사용된다. 뮤텍스를 공유되지 않는 데이터에 사용하지 말고, 프로그램 논리에 의해 한 번에 하나의 쓰레드만이 개별 데이터 구조에 액세스하고 있는 것이 보장된다면, 뮤텍스를 사용하지 말라.

둘째, 공유 데이터가 사용되고 있으면, 뮤텍스를 읽기와 쓰기 양쪽 모두에 사용하라. 읽기와 쓰기 섹션을 pthread_mutex_lock() 와 pthread_mutex_unlock()로 둘러싸거나, 프로그램의 불변성(invariant)이 일시적으로 깨졌을 때마다 읽기와 쓰기 섹션을 사용해야 한다. 코드를 단일 쓰레드의 시각에서 살펴보는 방식을 알아야하며, 프로그램에 있는 각각의 쓰레드가 메모리를 일관성있고 순조롭게 보도록 해야 한다. 뮤텍스의 사용법을 알기 위해 자신의 코드를 쓰는 데는 아마 몇 시간이 걸리겠지만, 뮤텍스는 곧 익숙해질 것이며 *너무* 많이 생각하지 않고도 뮤텍스를 적합하게 사용할 수 있을 것이다.


호출하기: 초기화

이제는 뮤텍스의 다른 사용법을 알아볼 시간이다. 첫째로 초기화에 대해 알아보겠다. thread3.c exmple에서는 정적인 초기화 방식이 사용되었다. 여기에는 pthread_mutex_t 변수의 선언과 그것에 상수 PTHREAD_MUTEX_INITIALIZER를 할당하는 것이 포함된다 :


pthread_mutex_t mymutex=PTHREAD_MUTEX_INITIALIZER;

아주 쉽다. 하지만 동적으로 뮤텍스를 생성할 수도 있다. malloc()을 사용하여 새로운 뮤텍스를 배당할 때는 언제든지 이런 동적인 방식을 사용해야 한다. 이 경우, 정적 초기화 방식은 작동되지 않으며, 다음과 같이 pthread_mutex_init() 루틴이 사용되어야 한다 :


int pthread_mutex_init( pthread_mutex_t *mymutex, const pthread_mutexattr_t *attr)

알려진 바와 같이, pthread_mutex_init()는 뮤텍스로 초기화하기 위하여 이미 배당된 메모리 영역을 가리키는 포인터를 받아들인다. 두 번째 인수로서, pthread_mutexattr_t 포인터 또한 선택적으로 받아들일 수 있다. 이 구조는 여러 가지의 뮤텍스 속성을 설정하는데 사용될 수 있다. 하지만 보통 이러한 속성들은 요구되지 않으므로, NULL을 지정하는 것이 정상이다.


pthread_mutex_lock(pthread_mutex_t *mutex)

pthread_mutex_lock()은 잠글 뮤텍스의 하나의 포인터를 수용한다. 뮤텍스가 이미 잠겨있다면, 호출한 함수(caller)는 휴면 상태가 될 것이다. 함수가 리턴될 때, 호출한 함수는 (분명하게) 깨어나서 잠금을 실행할 것이다. 이 호출은 성공시에 0을 리턴하고 실패시에 0이 아닌 에러코드를 리턴할 것이다.


pthread_mutex_unlock(pthread_mutex_t *mutex)

pthread_mutex_unlock()은 pthread_mutex_lock()와 상보적이며, 이미 잠겨있는 뮤텍스의 잠금을 해제한다. 사용자가 잠근 뮤텍스는 (성능이 향상되도록) 가능하면 신속하고 안전하게 잠금 해제해야 한다. 그리고 사용자가 잠그지 않은 뮤텍스를 잠금 해제해서는 안 된다 (그렇지 않으면, pthread_mutex_unlock() 호출은 0이 아닌 EPERM 리턴 값으로 실패할 것이다).


pthread_mutex_trylock(pthread_mutex_t *mutex)

이 호출은 쓰레드가 다른 어떤 일을 하는 동안(현재 뮤텍스는 잠겨있기 때문에) 뮤텍스를 잠그려고 할 때 편리하다. pthread_mutex_trylock()를 호출할 때, 사용자는 뮤텍스를 잠그려고 시도할 것이다. 뮤텍스가 현재 잠겨져 있지 않다면 잠글 것이며, 이 함수는 0을 리턴할 것이다. 하지만, 뮤텍스가 잠겨져 있다면 이 호출은 중단되지 않을 것이다. 오히려, 0 이 아닌 EBUSY 오류 값을 리턴할 것이다. 그러면 사용자는 비즈니스를 시작하고 나중에 잠금을 시도할 수 있다.


조건 대기

뮤텍스는 쓰레디드 프로그램에 필요한 툴이지만 만능은 아니다. 예를 들어, 사용자의 쓰레드가 공유 데이터에 어떤 조건이 나타나기를 기다린다면 어떻게 될까? 사용자의 코드는 그 값의 변화를 점검하면서 반복적으로 뮤텍스를 잠그고 잠금 해제할 수 있을 것이다. 동시에 사용자 코드는 다른 코드가 합리적인 타임 프레임에서 변화를 탐지하는데 필요한 비지 루프(busy-loop)를 만들 수 있도록 뮤텍스를 잠금 해제할 것이다.

호출 쓰레드를 잠시동안 휴면 상태로 둘 수 있지만(각 체크사이의 3초간), 그러면 사용자의 쓰레디드 코드의 반응은 최적 상태로 유지되지 않을 것이다. 정말로 필요한 것은 어떤 조건이 충족될 동안 쓰레드를 휴면 상태로 두는 방법이다. 조건이 충족되면 쓰레드를 깨우는 방법이 필요하다. 이렇게 할 수 있다면, 쓰레디드 코드는 능률적으로 되며 가치있는 뮤텍스 잠금을 묶어두지 않을 것이다. 이것이 바로 POSIX 조건 변수가 사용자를 위하여 할 수 있는 일이다!

Linux threads, 참조


필자소개

Daniel Robbins는 Gentoo Technologies, Inc.의 회장/CEO이고, Gentoo Project의 핵심 설계자이며, Caldera OpenLinux Unleashed, SuSE Linux Unleashed, Samba Unleashed의 저자이다

잘못된 도움말 신고

부정사용 신고

감사합니다. 이 항목은 운영자가 관심을 표시했습니다.


잘못된 도움말 신고

부정사용 신고

제출실패 신고. 나중에 다시 실행해주세요.


디벨로퍼웍스 로그인


IBM ID가 필요하세요?
IBM ID를 잊으셨습니까?


비밀번호를 잊으셨습니까?
비밀번호 변경

developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관.

 


developerWorks에 처음 로그인하면 developerWorks프로파일이 생성됩니다.귀하의 프로파일에서 동의하신 내용이 공개되지만 이 사항은 언제든지 변경 가능합니다. 귀하의 성명(숨김으로 체크되어 있어도 표시됩니다)과 디스플레이 이름은 게시한 컨텐츠나 사이트 엑세스시 표시됩니다.

화면상에 보여지는 닉네임을 정하세요.

처음 developerWorks에 로그인할 때 프로파일이 작성되므로, 이를 위해 디스플레이 이름을 선택해야 합니다. 선택하신 디스플레이 이름은 developerWorks에 게시한 컨텐츠에 표시됩니다.

3글자 이상 31글자 이하의 길이로 사용 가능합니다. dW커뮤니티 내에서는 보안상 이메일주소를 제외한 다른 이름을 지정하셔야 합니다.

3개의 &이나 대쉬를 포함해주시고 31글자내로 제한해주세요.


developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관.

 


아티클 순위

의견

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=20
Zone=리눅스
ArticleID=18064
ArticleTitle=Common Threads: POSIX 쓰레드, Part 2
publish-date=08012000
author1-email=
author1-email-cc=

태그

Help
검색 필드를 사용하여 My developerWorks 내에서 해당 태그가 사용된 모든 종류의 컨텐츠를 검색하십시오.

태그를 더 많이 보거나 적게 보기 위해 슬라이더 막대를 사용하십시오.

인기 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 최고 인기 태그를 보여줍니다.

내 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 귀하의 태그를 보여줍니다.

검색 필드를 사용하여 My developerWorks 내에서 해당 태그가 사용된 모든 종류의 컨텐츠를 검색하십시오. 인기 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 최고 인기 태그를 보여줍니다. 내 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 귀하의 태그를 보여줍니다.