복잡한 락 안 쓰고 쓰레드 풀 가동하기. 보통 연산중심의 프로그램에서는 멀티쓰레드를 과하게 쓰면 CPU 풀로 쓰면서 시스템이 맛탱이 간다. IO 한다고 놀 시간이 없다. 컨테이너 기반에서는 아마도 상세한 쿼타 제한이 있겠지만 보통은 대충 쓰면 다른 세입자나 프로세스에 민폐끼친다.
그래서 쓰레드 수 제한을 걸어야 하는디, 그냥 join으로 기다리면 노는 쓰레드가 생긴다. 1번 쓰레드가 빨리 끝나고 2번 쓰레드가 주구장창 일하고 있는 경우가 그렇다. 방법은 detach인데, 그러면 이제부터 락 문제가 생기는데 대개 귀찮다. 컨디셔널 변수에 뮤텍스 얹고 시그널 뿌리고 아오 귀찮아. 다른 사람들이 어떻게 쉽게 푸는지 잘 모르겠다. 내가 본 코드들은 거의 뮤텍스에 동기화를 쌈싸먹고 있어서 코드를 보는 순간 지지를 선언하게 된다.
나는 간단하게는 다음과 같이 한다. 가끔 아토믹 확장으로 수제-_-스핀락을 쓰기도 한다. usleep이 거슬리지만 대개는 join으로 기다리는 것 보다는 문제 없다. 대부분의 삽질 시간을 쓰레드 컨텍스트에서 소모하고 있기 때문이다. usleep(0)을 쓰면 컨텍스트를 전환하며 cpu 입장에서는 숨통이 트인다. 안 쓰면 cpu 하나는 쌩으로 뺑이친다. 즉 아래 예제에서 usleep(0)이라도 넣지 않으면 cpu 사용률은 500%를 친다. 넣으면 401% 정도로 쇼부본다.
중간에 보이는 __sync 함수는 아토믹 확장 함수 중에 하나다. 아마도 앞뒤에 asm 으로 간단한 경량 스핀락과 메모리 배리어가 들어있겠지만 자세히는 안 봤다. 여튼 대충 동작하는데 문제 없는 것 같다.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
int th_pool;
#define TH_POOL_MAX 4
void *go(void *arg)
{
int *p = (int *)arg;
int i;
pthread_detach(pthread_self());
for (i = 0; i < 100000; i++) {
(*p)++;
}
__sync_fetch_and_sub(&th_pool, 1);
pthread_exit((void *) 0);
}
int main(int argc, char *argv[])
{
int i;
pthread_t th[100];
int count[100];
int t;
for (i = 0; i < 100; i++) {
count[i] = 0;
pthread_create(&th[i], NULL, &go, &count[i]);
__sync_fetch_and_add(&th_pool, 1);
while(th_pool == TH_POOL_MAX) {
usleep(1000);
}
}
while(th_pool > 0) {
usleep(10000);
}
t = 0;
for (i = 0; i < 100; i++) {
t += count[i];
}
printf("t = %d\n", t);
return 0;
}