-
RCU (Read-Copy Update)개발/컴퓨터사이언스 2018. 10. 30. 21:41
공유중인 값을 읽는 쓰레드와 업데이트하는 쓰레의 개수가 각각 하나인 경우에는 경우에는 spin_lock을 이용해도 별 어려움 없이 동기화 작업을 수행 할 수 있다. 그런데 값을 읽는 프로세스가 두개 이상이 될 때부터는 동기화 작업이 골치가 아파진다. 아래의 코드처럼 두개 이상의 쓰레드가 공유중인 val 값을 읽으려고 하고 한개의 쓰레드가 값을 업데이트 하고 있다고 해보자. 본래의 목적은 reader를 호출하는 thd[0]와 thd[1] 쓰레드가 writer를 호출하는 thd[2] 쓰레드가 업데이트 하는 값을 동일하게 읽는 것이었다.
reader 두개를 writer보다 앞서서 생성한 덕분에 초반에는 어느 정도 동기화가 유지가 되겠지만 시간이 지나면서 쓰레드의 priority 값이 달라지게되고 어떤 기점에서 thd[2] 쓰레드가 thd[0]와 thd[1] 사이에 실행될 수도 있다. 이런 경우 thd[0]와 thd[1]은 같은 값을 읽지못하게 된다.
pthread_spinlock_t lock; int val; void *reader() { int read; while (1) { pthread_spin_lock(&lock); read = val; printf("reader1: %d\n", val); pthread_spin_unlock(&lock); } } void *writer() { while(1) { pthread_spin_lock(&lock); val++; pthread_spin_unlock(&lock); } } int main() { pthread_t thd[3]; pthread_spin_init(&lock, PTHREAD_PROCESS_SHARED); pthread_create(&thd[0], NULL, reader, NULL); pthread_create(&thd[1], NULL, reader, NULL); pthread_create(&thd[2], NULL, writer, NULL); pthread_join(thd[0], NULL); pthread_join(thd[1], NULL); pthread_join(thd[2], NULL); printf("DDD\n"); return 1; }
이런 경우를 위해 리눅스에서는 RCU(Read-Copy Update)라는 라이브러리를 제공한다. RCU를 사용하면 여러 개의 읽기 작업과 한 개의 쓰기 작업의 동기화를 구현 할 수 있다. 단, 여러 개의 쓰기 작업의 동기화는 지원이 안된다. LWN에서 소개한 예시코드로 간단히 사용 방법을 익혀보자.
struct foo { struct list_head list; int a; int b; int c; }; LIST_HEAD(head); /* . . . */ p = kmalloc(sizeof(*p), GFP_KERNEL); p->a = 1; p->b = 2; p->c = 3; list_add_rcu(&p->list, &head); // Thread1 rcu_read_lock(); list_for_each_entry_rcu(p, head, list) { do_something_with(p->a, p->b, p->c); } rcu_read_unlock(); // Thread2 q = kmalloc(sizeof(*p), GFP_KERNEL); *q = *p; q->b = 2; q->c = 3; list_replace_rcu(&p->list, &q->list); synchronize_rcu(); kfree(p);
11~15 라인에서는 `foo`구조체 포인터로 선언된 `p`변수에 메모리 공간을 할당하고 값을 대입한 후 `list_add_rcu`라는 함수를 이용해 리스트에 넣는 작업이다. `list_head` 변수가 확장성이 좋아 RCU에서도 list를 이용해 여러 개의 변수를 관리할 수 있도록 했다.
Thread1 은 읽는 작업을 수행하는 reader다. `p`값을 읽어오기 전에 `rcu_read_lock()` 함수를 호출해서 접근 권한을 얻고 작업을 수행한 다음에는 `rcu_read_unlock()`으로 lock을 풀어준다. 다른 쓰레드도 읽는 작업을 수행한다면 위와 동일한 코드를 탄다.
Thread2는 rcu 리스트의 값을 업데이트하는 쓰레드다. `list_replace_rcu()` 함수는 첫번째 인자의 값을 두번째 인자의 값으로 바꾸는 함수다. 그리고 바로 아래 `synchronize_rcu()`는 다른 쓰레드에서 rcu 리스트의 값을 읽는 작업이 모두 종료될 때 까지 기다린 후 수정한 정보를 업데이트 한다. 이 함수가 호출되지 않으면 RCU 리스트 수정 함수를 호출해도 reader쪽에서 읽는 값은 동일하다. 이 함수를 이용해 읽는 작업과 쓰는 작업의 동기화를 맞춰 줄 수 있다.
* 더 상세한 동작 메커니즘을 알고 싶으신 분은 LWN 을 참조하면 좋을 것 같다.
'개발 > 컴퓨터사이언스' 카테고리의 다른 글
프로스세와 스레드 (0) 2021.06.05 스핀락, 뮤텍스, 세마포어 (0) 2018.11.07 Cgroup (Control Group) (0) 2018.09.15 CPU pinning과 taskset (0) 2018.08.27 스핀락 (0) 2018.07.23