Process Synchronization

서로 다른 프로세스를 사용한다면, 동기화할 필요가 없다고 생각하시지 않았습니까? 만약 프로세스 내에서 race condition이 존재하지 않더라도 프로세스가 주변 system과 상호작용하는 경우엔 어떻게 될까요? 다음 예를 생각해봅시다.

void write_string(const char *data) {
    int fd = open("my_file.txt", O_WRONLY);
    write(fd, data, strlen(data));
    close(fd);
}

int main() {
    if(!fork()) {
        write_string("key1: value1");
        wait(NULL);
    } else {
        write_string("key2: value2");
    }
    return 0;
}


프로그램이 컴파일되어 실행될 때, system call이 실패하지 않는다면 아래와 같은 결과를 보게 될 것입니다.

key1: value1
key2: value2

또는

key2: value2
key1: value1

Interruption

대부분의 system call은 interrupt 될 수 있습니다. 즉 운영체제는 프로세스를 멈출 경우를 대비해 진행중인 system call을 멈출 수 있습니다. 그러므로 fork, wait, open, close는 실패를 방지해 일반적으로 끝까지 수행됩니다. 하지만 만약 write가 실패한다면 어떻게 될까요? 만약 write가 실패해서 아무것도 쓰여지지 않는다면 key1: value1 또는 key2: value2와 같이 하나의 write만 수행됩니다. 이러한 데이터의 손실은 부정확하지만 다른 파일을 덮어쓰지는 않습니다. 만약 write가 쓰여지는 중에 interrupt가 발생하면 어떻게 될까요? 
key2: key1: value1

예를 들어 ket2: 를 쓰는 중간에 interrupt가 발생해 위와 같은 결과가 나타날 수 있습니다.


Solution

그렇다면 어떻게 해야 위와 같은 경우를 방지할 수 있을까요?
이런 경우 shared mutex를 사용해야 합니다. 아래 코드를 살펴봅시다.

pthread_mutex_t * mutex = NULL;
pthread_mutexattr_t attr;

void write_string(const char *data) {
    pthread_mutex_lock(mutex);
    int fd = open("my_file.txt", O_WRONLY);
    int bytes_to_write = strlen(data), written = 0;
    while(written < bytes_to_write) {
        written += write(fd, data + written, bytes_to_write - written);
    }
    close(fd);
    pthread_mutex_unlock(mutex);
}

int main() {
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
    pmutex = mmap (NULL, sizeof(pthread_mutex_t), 
                PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANON, -1, 0);
    pthread_mutex_init(pmutex, &attrmutex);
    if(!fork()) {
        write_string("key1: value1");
        wait(NULL);
        pthread_mutex_destroy(pmutex);
        pthread_mutexattr_destroy(&attrmutex); 
        munmap((void *)pmutex, sizeof(*pmutex));
    } else {
        write_string("key2: value2");
    }
    return 0;
}



main에서는 공유되어지는 메모리를 사용하는 process shared mutex를 초기화합니다. mmap이 하는 일은 좀 더 나중에 다루겠지만, 프로세스간에 공유되는 메모리를 생성할 때 사용된다고 생각해 주세요. 특별한 메모리를 사용하는 pthread_mutex_t도 평소와 같이 초기화할 수 있습니다.  write 실패에 대비해 write call을 while loop안에서 실행하여 쓸 바이트가 남아있다면 계속 write를 합니다.  이제 다른 시스템 콜을 생각해보면, 더 많은 경쟁 조건을 고려해야 합니다.

대부분의 프로그램은 분리된 파일에 써서 이러한 문제를 피하려고 하지만, 프로세스간에도 mutex가 존재한다는 것을 알고 있으면 유용합니다.


What else can you do?

mutex뿐만이 아니라 이전에 배웠던 barrier, semaphores, condition variable 모두 공유된 메모리에 비슷한 방식으로 사용이 가능합니다.


Okay, so when would I use this?

임의의 메모리 주소가 race condition의 후보가 될 걱정을 하지 않아도 됩니다. 즉, mmap이나 파일과 같은 system resource 바깥의 영역만 조심하면 됩니다.


하나의 프로세스가 실패해도 격리가 잘 되어 시스템이 손상되지 않습니다.


스레드가 많다면 프로세스를 만드는 데 시스템 부하가 줄어들 것입니다.


Do I need to memorize the specifics?

그럴 필요 없이, mutex나 다른 동기화 요소들이 프로세스 사이에서도 사용될 수 있다는 것만 기억하면 됩니다!


Posted by 몰랑&봉봉
,