251027

lililllilillll·2025년 10월 27일

개발 일지

목록 보기
337/350

✅ 한 것들


  • 이력서, 포폴 수정
  • 윤성우의 열혈 TCP/IP 소켓 프로그래밍


📖 윤성우의 열혈 TCP/IP 소켓 프로그래밍


18-3 쓰레드의 문제점과 임계영역

두 쓰레드가 같은 값에 동시에 접근하면,
연산 끝낸 후에 결과 저장하기 전에
다음 쓰레드가 접근해버릴 수 있기 때문에 문제 발생.

동기화 필요.

임계영역

  • 함수 내에 둘 이상에 쓰레드가 동시에 실행하면 문제 일으키는 코드 블록
  • 전역 변수 num 자체는 임계 영역이 아니다

18-4 쓰레드 동기화

동기화 필요한 상황

  • 동일한 메모리 영역 동시 접근 발생
  • 동일한 메모리 영역에 접근하는 쓰레드 실행 순서 지정해야 하는 상황
    • ex) 쓰레드 A는 메모리에 갖다놓고, 쓰레드 B는 그 값을 가져감

뮤텍스 (Mutex)

  • Mutual Exclusion의 줄임말. 쓰레드의 동시 접근 허용하지 않는다.
  • pthread_mutex_init() : 자물쇠 생성
  • pthread_mutex_destroy() : 자물쇠 소멸
  • pthread_mutex_lock() : 자물쇠 잠그기
  • pthread_mutex_unlock() : 자물쇠 풀기
  • 임계영역 빠져나갈 때 unlock 호출 안 하면 lock 호출했던 다른 쓰레드는 무한 블로킹된다
void* thread_inc(void* arg)
{
	int i;
	pthread_mutex_lock(&mutex);
	for(i=0; i<500000; i++)
		num+=1;
	pthread_mutex_unlock(&mutex);
	return NULL;
}

void* thread_des(void* arg)
{
	int i;
	for(i=0; i<500000; i++)
	{
		pthread_mutex_lock(&mutex);
		num-=1;
		pthread_mutex_unlock(&mutex);
	}
	return NULL;
}
  • inc처럼 임계영역 넓게 잡으면 lock 호출 횟수 줄어들어서 성능 좋아지지만, 다른 쓰레드 접근 불가

세마포어 (Semaphore)

  • sem_init() : 세마포어 생성
  • sem_destroy() : 세마포어 소멸
  • sem_post() : 세마포어 값 1 증가
  • sem_wait()
    • 세마포어 값 1 감소
    • 세마포어 값이 0일 때 호출하면 다시 1 될때까지 블로킹
static sem_t sem_one;
static sem_t sem_two;
static int num;

int main(int argc, char *argv[])
{
	pthread_t id_t1, id_t2;
	sem_init(&sem_one, 0, 0);
	sem_init(&sem_two, 0, 1);
	
	pthread_create(&id_t1, NULL, read, NULL);
	pthread_create(&id_t2, NULL, accu, NULL);

	pthread_join(id_t1, NULL);
	pthread_join(id_t2, NULL);

	sem_destroy(&sem_one);
	sem_destroy(&sem_two);
	return 0;
}

void* read(void* arg)
{
	int i;
	for(i=0; i<5; i++)
	{
		fputs("Input num: ", stdout);
		sem_wait(&sem_two);
		scanf("%d", &num);
		sem_post(&sem_one);
	}
	return NULL;
}
void* accu(void* arg)
{
	int sum=0, i;
	for(i=0; i<5; i++)
	{
		sem_wait(&sem_one);
		sum+=num;
		sem_post(&sem_two);
	}
	printf("Result: %d \n", sum);
	return NULL;
}
  • wait는 화장실 쓸게요, post는 화장실 다 썼어요
  • read에서 two를 쓴다고 선언하면, accu에서 two 써도 된다고 할 때까지 다음 read 진행 막아줌
  • read에서 one 써도 된다고 하면, 그제야 accu가 돌아감

18-5 쓰레드의 소멸과 멀티쓰레드 기반의 다중접속 서버의 구현

쓰레드 소멸

  • pthread_join() : 쓰레드 소멸해주지만, 종료될 때까지 블로킹됨
  • pthread_detach() : 종료되지 않은 쓰레드 종료되지도 않고, 블로킹되지도 않음.
int clnt_cnt=0;
int clnt_socks[MAX_CLNT];
pthread_mutex_t mutx;

int main(int argc, char *argv[])
{
	int serv_sock, clnt_sock;
	struct sockaddr_in serv_adr, clnt_adr;
	int clnt_adr_sz;
	pthread_t t_id;
	if(argc!=2) {
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}

	pthread_mutex_init(&mutx, NULL);
	serv_sock=socket(PF_INET, SOCK_STREAM, 0);

	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
	serv_adr.sin_port=htons(atoi(argv[1]));

	if(bind(serv_sock, (struct sockaddr*) &serv_adr, sizeof(serv_adr))==-1)
		error_handling("bind() error");
	if(listen(serv_sock, 5)==-1)
		error_handling("listen() error");

	while(1)
	{
		clnt_adr_sz=sizeof(clnt_adr);
		clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);

		pthread_mutex_lock(&mutx);
		clnt_socks[clnt_cnt++]=clnt_sock;
		pthread_mutex_unlock(&mutx);

		pthread_create(&t_id, NULL, handle_clnt, (void*)&clnt_sock);
		pthread_detach(t_id);
		printf("Connected client IP: %s", inet_ntoa(clnt_adr.sin_addr));
	}
	close(serv_sock);
	return 0;
}

void* handle_clnt(void* arg)
{
	int clnt_sock=*((int*)arg);
	int str_len=0, i;
	char msg[BUF_SIZE];

	while((str_len=read(clnt_sock, msg, sizeof(msg)))!=0)
		send_msg(msg, str_len);

	pthread_mutex_lock(&mutx);
	for(i=0; i<clnt_cnt; i++) // remove disconnected client
	{
		if(clnt_sock == clnt_socks[i])
		{
			while(i++<clnt_cnt-1)
				clnt_socks[i]=clnt_socks[i+1];
			break;
		}
	}
	clnt_cnt--;
	pthread_mutex_unlock(&mutx);
	close(clnt_sock);
	return NULL;
}
void send_msg(char* msg, int len) // send to all
{
	int i;
	pthread_mutex_lock(&mutx);
	for(i=0; i<clnt_cnt; i++)
		write(clnt_socks[i], msg, len);
	pthread_mutex_unlock(&mutx);
}

chat 서버

  • 소켓 cnt 올리고 할당할 때 lock 사용
  • 클라 소켓 만들어지면, 그 소켓을 위한 쓰레드 따로 만들어준다
  • read 계속 하면서 메시지 받을 때마다 모든 클라에게 뿌린다
  • 메시지 끝나면 lock 걸고 클라 소켓 자리 앞으로 다 땡김
int main(int argc, char *argv[])
{
	int sock;
	struct sockaddr_in serv_addr;
	pthread_t snd_thread, rcv_thread;
	void* thread_return;
	if(argc!=4) {
		printf("Usage : %s <IP> <port> <name>\n", argv[0]);
		exit(1);
	}

	sprintf(name, "[%s]", argv[3]);
	sock=socket(PF_INET, SOCK_STREAM, 0);

	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family=AF_INET;
	serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
	serv_addr.sin_port=htons(atoi(argv[2]));

	if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1)
		error_handling("connect() error");

	pthread_create(&snd_thread, NULL, send_msg, (void*)&sock);
	pthread_create(&rcv_thread, NULL, recv_msg, (void*)&sock);
	pthread_join(snd_thread, &thread_return);
	pthread_join(rcv_thread, &thread_return);
	close(sock);
	return 0;
}

void* send_msg(void* arg)
{
	int sock=*((int*)arg);
	char name_msg[NAME_SIZE+BUF_SIZE];
	while(1)
	{
		fgets(msg, BUF_SIZE, stdin);
		if(!strcmp(msg,"q\n")||!strcmp(msg,"Q\n"))
		{
			close(sock);
			exit(0);
		}
		sprintf(name_msg,"%s %s",name,msg);
		write(sock, name_msg, strlen(name_msg));
	}
	return NULL;
}
void* recv_msg(void* arg)
{
	int sock=*((int*)arg);
	char name_msg[NAME_SIZE+BUF_SIZE];
	int str_len;
	while(1)
	{
		str_len=read(sock, name_msg, NAME_SIZE+BUF_SIZE-1);
		if(str_len==-1)
			return (void*)-1;
		name_msg[str_len]=0;
		fputs(name_msg, stdout);
	}
	return NULL;
}
  • 메시지 받는 쓰레드와 보내는 쓰레드로 나눈다

Part 03 윈도우즈 기반 프로그래밍

Chapter 19 Windows에서의 쓰레드 사용

19-1 커널 오브젝트 (Kernel Objects)

운영체제가 만드는 리소스 : 프로세스, 쓰레드, 파일, 세마포어, 뮤텍스 등
커널 오브젝트

  • 리소스 관리 목적으로 정보 기록하기 위해 생성한 데이터 블록
  • 소유자는 프로세스가 아니라 커널(운영체제)
  • 즉, 생성, 관리, 소멸까지 모두 운영체제의 몫

19-2 윈도우 기반의 쓰레드 생성

프로그램이 시작될 때 main 함수를 호출하는 주체는 쓰레드
프로세스는 자원/주소 공간, 쓰레드는 실행 흐름
윈도우 쓰레드 소멸 시점은 쓰레드가 맨 처음 호출된 main 함수가 반환하는 시점

CreateThread()

  • 쓰레드 생성
  • 운영체제가 관리를 위해 커널 오브젝트도 생성
  • 커널 오브젝트 구분자인 핸들도 반환
  • 만들어진 쓰레드는 C/C++ 표준 함수에 대해 안정적으로 동작하지 않음
    • _beginthreadex() 쓰면 해결됨
int main(int argc, char* argv[])
{
	HANDLE hThread;
	unsigned threadID;
	int param = 5;

	hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, (void*)&param, 0, &threadID);
	if (hThread == 0)
	{
		puts("_beginthreadex() error");
		return -1;
	}
	Sleep(3000);
	puts("end of main");
	return 0;
}
unsigned WINAPI ThreadFunc(void* arg)
{
	int i;
	int cnt = *((int*)arg);
	for (i = 0; i < cnt; i++)
	{
		Sleep(1000); puts("running thread");
	}
	return 0;
}
  • _beginthreadex()의 반환 값은 원래 unsinged인데, 역시 정수형인 HANDLE로 저장
  • _beginthreadex()가 요구하는 호출규약 지키기 위해 WINAPI 삽입

쓰레드를 구분하는 값에는 ID도 있고 핸들도 있다

  • 핸들 : 프로세스 달라지면 중복 가능
  • ID : 프로세스 영역 넘어서도 중복 안됨

메인 쓰레드 소멸하면 생성된 쓰레드들 다 사라져서, 5개가 아니라 3개만 호출되고 끝난다.

19-3 커널 오브젝트의 두 가지 상태

signaled 상태 : 종료됨
non-signaled 상태 : 안 종료됨

프로세스나 쓰레드 종료되면 해당 커널 오브젝트는 signaled 상태로 변경됨

WaitForSingleObject()

  • 커널 오브젝트에 대해 signaled 상태인지 확인
  • signaled 상태가 되면 반환
  • auto-reset 모드 커널 오브젝트 : 반환되면 다시 non-signaled
  • manual-reset 모드 커널 오브젝트 : 반환되도 다시 안 돌아감

WaitForMultipleObjects()

  • 여러 커널 오브젝트 대상으로 상태 확인
	hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, (void*)&param, 0, &threadID);
	if (hThread == 0)
	{
		puts("_beginthreadex() error");
		return -1;
	}
	
	if((wr=WaitForSingleObject(hThread, INFINITE))==WAIT_FAILED)
	{
		puts("thread wait error");
		return -1;
	}

	printf("wait result: %s \n", (wr == WAIT_OBJECT_0) ? "signaled" : "time-out");

쓰레드 기다려주면 5개 다 출력된다



profile
너 정말 **핵심**을 찔렀어

0개의 댓글