교수님: 일을 반드시 여러 사람과 함께 해야 효율이 높아지나요? 과제를 두 명이서 같이 한다고 생산성이 반드시 두 배가 되던가요? 뭐 프리라이더도 있고, 서로 안 맞아서 싸워서 혼자 하느니만도 못하게 될 수도 있고.. 뭐 커뮤니케이션 안 맞고, 로드 밸런스 안 맞고? issue가 많죠?
You know there's some kind of cost you need to pay in order to run things in pararell. So that's basically what this module is about. So we're going the last module is about concurrent programming!
이재욱 교수님의 5월 30일 목요일자 명강의, 정리 레츠고❤️
"doing several thigs at the same time"본 강의에서 다룰 concurrency는 OS level의 concurrency와는 다르다. 여러 개의 task를 수행하는 것에 있어 동시성이 아니라, 하나의 작업 내에서 어떻게 concurrent 하게 작업을 수행할 것인지에 관한 논의.
most useful for data pararellism and task pararellism.
(+ 추가. GPU에서 CUDA 같은 것도 data para~의 대표적인 예시임)
예시를 통해 이해하는 것이 빠름.
[예시] C[i] = A[i] + B[i] 벡터 연산

한 번에 다 수행하기만을 기다리면 너무 오래걸리니깐 4개로 나눠서 수행하도록 하기. 방식은 여러 가지가 있을 수 있는데 효율성도 고려해서 나누는 게 좋다. (cache miss/hit 측면에서도 생각해보면 좋음)

distribute different tasks to multiple execution threads.
하나의 프로그램 안에서 task 별로 thread 다르게 하기.
대표적인 예시: 웹 서버

interleaved 개념에 대해 부연설명: 어떤 프로그램의 일부를 다른 프로그램에 끼워 넣는 일. 결과적으로 2개의 프로그램이 동시에 수행되게 함.
보통 사용하는 16 코어 CPU 에서,
Q. What would be the optimal parallelism?
degree of parallelism and throughput?
설명
Concurrent programming is hard..😂
- Race
- Deadlocks
- Livelock / Starvation / Fairness
자식 프로세스나 부모 프로세스 중 누가 먼저 실행될지는 아무도 모른다.
아래 코드를 순차적인 코드로 바꾸려면 barrier()를 삽입해서 실행 순서를 정해줄 수 있다. 아래 코드에서 주석을 해제하면 무조건 parent가 먼저 실행되게 됨.
int main(int argc, char *argv[])
{
pid_t pid = fork();
if (pid > 0) {
printf("Hello from parent!\n");
// barrier();
}
else if (pid == 0) {
// barrier();
printf("Hello from child!\n");
}
else printf(“Cannot fork.\n");
retun EXIT_SUCCESS;

ㅋㅋㅋㅋㅋㅋㅋㅋㅋ
exclusive lock을 걸었을 때 서로가 서로를 기다리는 현상이 발생해서 아무것도 실행되지 못하는 상황을 가리킨다. 물고 물리는 관계~
p1.c: lock(A)-> lock(B)
p2.c: lock(B)-> lock(A)
p2은 p1의 A가 해제될 때까지 기다리고,
p1은 p2의 B가 해제될 때까지 기다리고, 결국 아무것도 시작하지 못한다
When people always jump in front of you in line(!)

livelock 이라고도 불리는 이유 (deadlock과 비교해서): 내가 차를 몰고 가고 있는데, 옆 차선의 차량이 끼어들기를 하려고 함. 근데 서로 양보해서 그 자리를 아무도 차지 하지 않는 거임. 그래서 갈까..?하고 속도를 더 내면 둘이 또 부딫힐까봐 또 못 가는 거임. 그래서 갈 수 있는데 못 가는 상황이니깐. livelock임.. ㅠㅠ
deadlock은 애초에 못 가서 못 가는 거니깐. (프로세스가 끝날때까지 계속 기다리고 있으니깐)
Iterative servers process one request at a time
Iterative Echo Server Example:

여러 클라이언트의 요청을 동시에 처리하기 어려움, concurrency가 필요하다!
Concurrent servers use multiple concurrent flows to serve multiple clients at the same time ^^
private memory by defaultshared memory by default
/// @brief main server routine accepting new connections.
/// Calls run_instance() for every client and blocks until the client exits.
/// @param listen_fd listening socket
void run_server(int listen_fd)
{
while (1) {
int client_fd;
struct sockaddr client;
socklen_t clientlen = sizeof(client);
client_fd = accept(listen_fd, &client, &clientlen);
if (client_fd > 0) {
printf(" connection from ");
dump_sockaddr(&client);
printf("\n");
run_instance(client_fd);
close(client_fd);
} else {
// print error message and abort on any error
perror("accept");
break;
}
}
}
/// @brief main server routine accepting new connections. Forks a new
/// instance for every client and calls run_instance() on it.
/// @param listen_fd listening socket
void run_server(int listen_fd)
{
// register signal handler
if (signal(SIGCHLD, reaper) == SIG_ERR) { … }
while (1) {
int client_fd;
struct sockaddr client;
socklen_t clientlen = sizeof(client);
client_fd = accept(listen_fd, &client, &clientlen);
if (client_fd > 0) {
printf(" connection from ");
dump_sockaddr(&client);
printf("\n");
if (fork() == 0) {
// close listening socket in client & set sock_fd to cfd for close at exit
close(listen_fd);
sock_fd = client_fd;
run_instance(client_fd);
exit(EXIT_SUCCESS);
}
close(client_fd);
} else {
// handle error
}
}
}
/// @brief signal handler for SIGCHLD, reaps all exited children
/// @param signal signal ID (==SIGCHLD)
void reaper(int signal)
{
while (waitpid(-1, 0, WNOHANG) > 0) ;
}

그래서 뭐가 어떻게 달라진건데? 가장 주요한 차이점은 자식 프로세스에서 클라이언트의 요청을 처리하도록 바꾸었다는 것이 코드에서 달라진 부분임.
또한 자식 프로세스가 종료될 때 시그널(SIGCHLD)을 처리할 핸들러를 등록하였다는 것도 달라진 점.
공통 부분 설명
서버는 계속해서 클라이언트의 연결을 대기하면서, 리스닝 소켓 listen_fd에서 클라이언트의 연결을 수락하고, 새로운 연결이 들어오면 client_fd를 반환.
client_fd = accept(listen_fd, &client, &clientlen);
달라진 부분 설명
자식 프로세스 생성 (fork())
자식 프로세스에서는 리스닝 소켓을 닫고(close(listen_fd)), run_instance(client_fd)를 호출하여 클라이언트의 요청을 처리
부모 프로세스는 클라이언트 소켓을 닫고(close(client_fd)), 다음 클라이언트 연결을 대기
// child process
if (fork() == 0) {
close(listen_fd);
sock_fd = client_fd;
run_instance(client_fd);
exit(EXIT_SUCCESS);
}
// parent process
close(client_fd);

parent process must close client_fd
child process must close listen_fd
부모 프로세스가
client_fd를 닫는 이유:
리소스 해제 및 파일 디스크립터 누수 방지
자식 프로세스가 클라이언트 소켓을 닫았을 때 실제로 소켓이 완전히 닫히도록 보장
자식 프로세스가listen_fd를 닫는 이유:
불필요한 리소스 점유 방지
리스닝 소켓이 여러 프로세스에서 관리되는 것을 방지하고, 부모 프로세스가 올바르게 새로운 클라이언트 연결을 처리할 수 있도록 보장
요약
이와 같은 리소스 관리와 프로세스 간 간섭 방지는 서버가 안정적으로 동작하도록 하는 중요한 요소입니다.
listening server process must close its copy of client_fd
forked-off echo server process should close its copy of listen_fd
listening server process must reap zombie children to avoid fatal memory leak
