C언어에서 멀티스레딩은 성능 향상과 동시에 데이터 무결성을 유지하기 위한 동기화 기법이 필수적입니다. 특히 읽기-쓰기 작업이 빈번한 경우, 효율적인 동기화 메커니즘이 필요합니다. 본 기사에서는 pthread_rwlock
을 활용해 읽기와 쓰기 작업을 효과적으로 동기화하는 방법을 다루며, 구현 예제와 함께 성능 최적화 방법도 소개합니다.
멀티스레딩 환경에서 동기화의 중요성
멀티스레드 프로그래밍에서는 여러 스레드가 동시에 데이터를 읽거나 쓸 수 있습니다. 이 과정에서 데이터 무결성을 유지하지 않으면 예기치 않은 동작이나 심각한 오류가 발생할 수 있습니다. 동기화는 이러한 문제를 해결하기 위해 스레드 간의 작업 순서를 조정하거나 특정 리소스에 대한 접근을 제어하는 기술입니다.
데이터 무결성
데이터 무결성을 보장하려면 스레드 간의 충돌을 방지해야 합니다. 예를 들어, 한 스레드가 데이터를 수정하는 동안 다른 스레드가 같은 데이터를 읽으면 부정확한 값이 반환될 수 있습니다.
동기화 없는 경우의 문제
동기화가 없는 경우 발생할 수 있는 주요 문제는 다음과 같습니다.
- Race Condition: 두 개 이상의 스레드가 동일한 리소스를 동시에 수정하려고 할 때 발생합니다.
- Deadlock: 잘못된 락 관리로 인해 여러 스레드가 서로 기다리는 상황에 빠집니다.
- Data Inconsistency: 읽기와 쓰기 작업이 충돌하여 데이터의 일관성이 손상됩니다.
동기화 기법의 필요성
동기화는 위와 같은 문제를 예방하고 다음과 같은 장점을 제공합니다.
- 작업의 순서를 보장
- 데이터 안정성과 신뢰성 확보
- 병렬 작업의 성능 향상
이와 같이 멀티스레드 환경에서 동기화는 안정적이고 효율적인 프로그램을 작성하기 위한 핵심 요소입니다.
읽기-쓰기 동기화란 무엇인가
읽기-쓰기 동기화는 여러 스레드가 동일한 데이터에 접근할 때, 읽기 작업과 쓰기 작업 간의 충돌을 방지하기 위해 사용되는 동기화 기법입니다. 이 방법은 읽기 작업이 쓰기 작업을 방해하지 않으면서도 쓰기 작업의 데이터 무결성을 보장합니다.
읽기와 쓰기 작업의 차이
- 읽기 작업: 데이터의 상태를 변경하지 않고 단순히 조회하는 작업입니다. 여러 스레드가 동시에 수행해도 데이터 무결성이 손상되지 않습니다.
- 쓰기 작업: 데이터를 수정하거나 업데이트하는 작업입니다. 이 작업은 데이터의 상태를 변경하므로 동시에 여러 스레드가 접근하면 충돌이 발생할 수 있습니다.
읽기-쓰기 락의 필요성
읽기-쓰기 락은 다음과 같은 이유로 중요합니다.
- 병렬성 향상: 읽기 작업이 많은 경우, 읽기 스레드가 동시에 데이터에 접근할 수 있어 성능이 향상됩니다.
- 데이터 무결성 보장: 쓰기 작업이 실행되는 동안에는 다른 스레드의 읽기와 쓰기를 차단하여 데이터의 일관성을 유지합니다.
- 효율적 리소스 관리: 단순한 mutex보다 읽기와 쓰기 간의 접근 권한을 세분화하여 리소스를 효율적으로 활용합니다.
읽기-쓰기 동기화의 동작 원리
읽기-쓰기 동기화는 다음과 같은 규칙에 따라 작동합니다.
- 여러 스레드가 동시에 읽기 락을 획득할 수 있습니다.
- 쓰기 락은 단일 스레드만 획득할 수 있으며, 다른 읽기 또는 쓰기 작업은 대기 상태가 됩니다.
- 쓰기 락이 해제되면 대기 중인 읽기 작업이나 쓰기 작업이 진행됩니다.
이러한 동작 원리는 멀티스레드 환경에서 성능을 유지하면서도 안정성을 확보하는 데 매우 효과적입니다.
pthread_rwlock의 구조와 기본 개념
pthread_rwlock
은 POSIX 스레드 라이브러리에서 제공하는 읽기-쓰기 락(Read-Write Lock)입니다. 이는 멀티스레드 환경에서 읽기 작업과 쓰기 작업을 효율적으로 동기화하기 위해 설계된 도구입니다.
pthread_rwlock의 주요 구성 요소
- 읽기 락(Read Lock)
- 여러 스레드가 동시에 읽기 작업을 수행할 수 있도록 허용합니다.
- 쓰기 락이 활성화되어 있지 않은 경우에만 획득할 수 있습니다.
- 쓰기 락(Write Lock)
- 단일 스레드만 쓰기 작업을 수행할 수 있도록 허용합니다.
- 다른 스레드의 읽기 락이나 쓰기 락이 활성화되어 있는 경우에는 획득할 수 없습니다.
pthread_rwlock의 주요 함수
pthread_rwlock
은 다음과 같은 주요 함수로 구성됩니다.
함수 | 설명 |
---|---|
pthread_rwlock_init | 읽기-쓰기 락을 초기화합니다. |
pthread_rwlock_destroy | 읽기-쓰기 락을 해제하고 자원을 반환합니다. |
pthread_rwlock_rdlock | 읽기 락을 획득합니다. |
pthread_rwlock_wrlock | 쓰기 락을 획득합니다. |
pthread_rwlock_unlock | 읽기 락 또는 쓰기 락을 해제합니다. |
pthread_rwlock_tryrdlock | 읽기 락을 시도적으로 획득합니다(즉시 실패 가능). |
pthread_rwlock_trywrlock | 쓰기 락을 시도적으로 획득합니다(즉시 실패 가능). |
pthread_rwlock의 특징
- 효율적인 동기화: 읽기 작업이 많은 환경에서 성능을 극대화할 수 있습니다.
- 단순한 인터페이스: 다양한 상황에 대응할 수 있는 함수 세트를 제공합니다.
- POSIX 표준 준수: 대부분의 UNIX 계열 운영체제에서 사용 가능합니다.
적용 시나리오
pthread_rwlock
은 다음과 같은 경우에 유용합니다.
- 데이터 구조가 읽기 작업에 자주 접근되고, 쓰기 작업은 드물게 발생하는 경우
- 여러 스레드가 동일한 데이터에 병렬로 접근해야 하는 경우
이러한 특성 덕분에 pthread_rwlock
은 읽기와 쓰기 작업을 구분하여 성능과 안정성을 동시에 제공하는 강력한 동기화 도구입니다.
pthread_rwlock 초기화 및 해제
pthread_rwlock
을 사용하기 위해서는 락을 초기화하고, 사용이 끝난 후 자원을 정리해야 합니다. 이 과정은 프로그램의 안정성과 자원 효율성을 보장합니다.
pthread_rwlock 초기화
락을 사용하기 전에 반드시 초기화해야 합니다. 초기화는 pthread_rwlock_init
함수를 통해 이루어지며, 기본 속성을 사용하거나 사용자 정의 속성을 지정할 수 있습니다.
기본 초기화 예제
#include <pthread.h>
#include <stdio.h>
pthread_rwlock_t rwlock;
int main() {
// 기본 초기화
if (pthread_rwlock_init(&rwlock, NULL) != 0) {
perror("pthread_rwlock_init failed");
return 1;
}
// 프로그램 로직
// 락 해제
pthread_rwlock_destroy(&rwlock);
return 0;
}
사용자 정의 속성을 이용한 초기화
#include <pthread.h>
#include <stdio.h>
pthread_rwlock_t rwlock;
pthread_rwlockattr_t attr;
int main() {
// 속성 객체 초기화
pthread_rwlockattr_init(&attr);
// 속성 설정(필요에 따라)
// pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP);
// 락 초기화
if (pthread_rwlock_init(&rwlock, &attr) != 0) {
perror("pthread_rwlock_init failed");
return 1;
}
// 속성 객체 해제
pthread_rwlockattr_destroy(&attr);
// 프로그램 로직
// 락 해제
pthread_rwlock_destroy(&rwlock);
return 0;
}
pthread_rwlock 해제
락 사용이 끝나면 pthread_rwlock_destroy
를 호출하여 자원을 반환해야 합니다. 해제를 하지 않을 경우 메모리 누수가 발생할 수 있습니다.
락 해제 예제
// 락 해제
if (pthread_rwlock_destroy(&rwlock) != 0) {
perror("pthread_rwlock_destroy failed");
}
초기화 및 해제 시 주의점
- 초기화 전에 락을 사용하면 정의되지 않은 동작이 발생할 수 있습니다.
- 락이 사용 중일 때
pthread_rwlock_destroy
를 호출하면 실패하며, 안전하게 해제하려면 모든 락이 해제된 상태를 보장해야 합니다. - 속성을 설정한 경우, 사용 후 반드시 속성 객체를 해제해야 합니다.
이 초기화와 해제 과정을 정확히 수행하면 pthread_rwlock
을 안정적이고 효율적으로 사용할 수 있습니다.
읽기 락과 쓰기 락의 사용법
pthread_rwlock
은 읽기 락(Read Lock)과 쓰기 락(Write Lock)을 제공하여 스레드 간의 동기화를 효율적으로 관리합니다. 각 락은 특정 상황에서 서로 다른 방식으로 동작합니다.
읽기 락의 사용법
읽기 락은 여러 스레드가 동시에 데이터에 접근할 수 있도록 허용합니다. 그러나 쓰기 락이 활성화된 경우 읽기 락은 대기 상태에 들어갑니다.
읽기 락 획득 예제
#include <pthread.h>
#include <stdio.h>
pthread_rwlock_t rwlock;
void *reader(void *arg) {
pthread_rwlock_rdlock(&rwlock);
printf("Reader %d is reading\n", *(int *)arg);
pthread_rwlock_unlock(&rwlock);
return NULL;
}
int main() {
pthread_rwlock_init(&rwlock, NULL);
// 여러 스레드에서 읽기 락 시뮬레이션
pthread_rwlock_destroy(&rwlock);
return 0;
}
쓰기 락의 사용법
쓰기 락은 단일 스레드만 데이터에 접근할 수 있도록 보장하며, 다른 모든 읽기 및 쓰기 락은 대기 상태로 전환됩니다.
쓰기 락 획득 예제
#include <pthread.h>
#include <stdio.h>
pthread_rwlock_t rwlock;
void *writer(void *arg) {
pthread_rwlock_wrlock(&rwlock);
printf("Writer %d is writing\n", *(int *)arg);
pthread_rwlock_unlock(&rwlock);
return NULL;
}
int main() {
pthread_rwlock_init(&rwlock, NULL);
// 여러 스레드에서 쓰기 락 시뮬레이션
pthread_rwlock_destroy(&rwlock);
return 0;
}
락 해제
읽기 락과 쓰기 락은 작업이 끝난 후 반드시 해제해야 합니다. 해제하지 않으면 다른 스레드가 자원을 사용할 수 없게 되어 프로그램이 멈출 수 있습니다.
pthread_rwlock_unlock(&rwlock);
읽기 락과 쓰기 락의 동작 비교
특성 | 읽기 락 | 쓰기 락 |
---|---|---|
동시 접근 | 여러 스레드 가능 | 단일 스레드만 가능 |
쓰기 대기 | 쓰기 작업 대기 | 다른 읽기/쓰기 작업 모두 대기 |
데이터 무결성 | 데이터 수정 불가(읽기 전용) | 데이터 수정 가능 |
사용 시 주의점
- 락 대기 상태: 쓰기 락이 활성화되면 모든 읽기 작업도 대기하므로, 잠재적인 병목 현상이 발생할 수 있습니다.
- 락 해제 확인: 반드시 락 해제(
pthread_rwlock_unlock
)를 호출하여 다른 스레드가 락을 사용할 수 있도록 해야 합니다. - 데드락 방지: 복잡한 락 계층 구조에서 데드락이 발생하지 않도록 락 획득 순서를 명확히 정의해야 합니다.
이러한 사용법을 익히면 pthread_rwlock
을 사용하여 읽기와 쓰기 작업을 안전하고 효율적으로 관리할 수 있습니다.
pthread_rwlock을 사용한 동기화 예제
아래는 pthread_rwlock
을 사용하여 읽기 스레드와 쓰기 스레드가 동시에 작업할 수 있도록 동기화를 구현한 예제입니다. 이 코드는 실시간 데이터를 처리하는 시뮬레이션을 보여줍니다.
코드 예제: 읽기-쓰기 동기화
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#define NUM_READERS 3
#define NUM_WRITERS 2
pthread_rwlock_t rwlock;
int shared_data = 0;
void *reader(void *arg) {
int id = *(int *)arg;
while (1) {
pthread_rwlock_rdlock(&rwlock); // 읽기 락 획득
printf("Reader %d reads shared_data: %d\n", id, shared_data);
pthread_rwlock_unlock(&rwlock); // 읽기 락 해제
sleep(1); // 읽기 간격
}
return NULL;
}
void *writer(void *arg) {
int id = *(int *)arg;
while (1) {
pthread_rwlock_wrlock(&rwlock); // 쓰기 락 획득
shared_data++;
printf("Writer %d updates shared_data to: %d\n", id, shared_data);
pthread_rwlock_unlock(&rwlock); // 쓰기 락 해제
sleep(2); // 쓰기 간격
}
return NULL;
}
int main() {
pthread_t readers[NUM_READERS], writers[NUM_WRITERS];
int reader_ids[NUM_READERS], writer_ids[NUM_WRITERS];
// rwlock 초기화
pthread_rwlock_init(&rwlock, NULL);
// 읽기 스레드 생성
for (int i = 0; i < NUM_READERS; i++) {
reader_ids[i] = i + 1;
pthread_create(&readers[i], NULL, reader, &reader_ids[i]);
}
// 쓰기 스레드 생성
for (int i = 0; i < NUM_WRITERS; i++) {
writer_ids[i] = i + 1;
pthread_create(&writers[i], NULL, writer, &writer_ids[i]);
}
// 스레드 종료 대기
for (int i = 0; i < NUM_READERS; i++) {
pthread_join(readers[i], NULL);
}
for (int i = 0; i < NUM_WRITERS; i++) {
pthread_join(writers[i], NULL);
}
// rwlock 해제
pthread_rwlock_destroy(&rwlock);
return 0;
}
코드 설명
- 공유 데이터
shared_data
는 읽기와 쓰기 작업의 대상이 되는 공유 변수입니다. - 읽기 스레드(reader)
읽기 스레드는pthread_rwlock_rdlock
을 사용하여 읽기 락을 획득한 후, 공유 데이터를 읽고 락을 해제합니다. - 쓰기 스레드(writer)
쓰기 스레드는pthread_rwlock_wrlock
을 사용하여 쓰기 락을 획득한 후, 공유 데이터를 수정하고 락을 해제합니다. - 락 초기화 및 해제
프로그램 시작 시pthread_rwlock_init
으로 락을 초기화하고, 종료 시pthread_rwlock_destroy
로 자원을 정리합니다.
출력 예시
Writer 1 updates shared_data to: 1
Reader 1 reads shared_data: 1
Reader 2 reads shared_data: 1
Reader 3 reads shared_data: 1
Writer 2 updates shared_data to: 2
Reader 1 reads shared_data: 2
Reader 2 reads shared_data: 2
...
핵심 포인트
- 읽기 락은 여러 스레드가 동시에 공유 데이터를 읽을 수 있게 합니다.
- 쓰기 락은 단일 스레드만 데이터를 수정할 수 있도록 보장합니다.
- 락을 적절히 사용하여 데이터의 무결성과 동시성 성능을 모두 확보할 수 있습니다.
이 예제를 통해 pthread_rwlock
을 사용하여 멀티스레드 환경에서 읽기-쓰기 동기화를 구현하는 방법을 이해할 수 있습니다.
성능 비교: Mutex와 Read-Write Lock
pthread_rwlock
과 pthread_mutex
는 모두 멀티스레드 환경에서 동기화를 제공하지만, 사용 목적과 성능은 다릅니다. 특정 작업 환경에서 어느 락이 더 적합한지 이해하기 위해 두 락의 성능과 동작을 비교합니다.
Mutex와 Read-Write Lock의 차이
특성 | Mutex | Read-Write Lock (pthread_rwlock ) |
---|---|---|
사용 목적 | 단일 락을 통해 데이터 보호 | 읽기-쓰기 접근 분리를 통한 최적화 |
동시성 수준 | 읽기와 쓰기를 모두 단일 락으로 제한 | 다수의 읽기 스레드 동시 접근 가능 |
락 유형 | 단일 락 | 읽기 락과 쓰기 락 구분 |
성능 | 읽기 작업이 적은 환경에서 유리 | 읽기 작업이 많은 환경에서 유리 |
성능 비교 테스트
읽기와 쓰기 작업 비율에 따라 두 락의 성능을 비교합니다.
테스트 환경
- 공유 데이터에 대해 90%는 읽기, 10%는 쓰기 작업을 수행
- 스레드 수: 10개
- 실행 시간 측정
테스트 코드 예시
#include <pthread.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#define NUM_THREADS 10
#define NUM_OPERATIONS 1000000
#define READ_RATIO 90 // 읽기 비율(%)
pthread_mutex_t mutex;
pthread_rwlock_t rwlock;
int shared_data = 0;
void *mutex_test(void *arg) {
for (int i = 0; i < NUM_OPERATIONS; i++) {
if (rand() % 100 < READ_RATIO) { // 읽기 작업
pthread_mutex_lock(&mutex);
int temp = shared_data; // 읽기
pthread_mutex_unlock(&mutex);
} else { // 쓰기 작업
pthread_mutex_lock(&mutex);
shared_data++; // 쓰기
pthread_mutex_unlock(&mutex);
}
}
return NULL;
}
void *rwlock_test(void *arg) {
for (int i = 0; i < NUM_OPERATIONS; i++) {
if (rand() % 100 < READ_RATIO) { // 읽기 작업
pthread_rwlock_rdlock(&rwlock);
int temp = shared_data; // 읽기
pthread_rwlock_unlock(&rwlock);
} else { // 쓰기 작업
pthread_rwlock_wrlock(&rwlock);
shared_data++; // 쓰기
pthread_rwlock_unlock(&rwlock);
}
}
return NULL;
}
int main() {
pthread_t threads[NUM_THREADS];
clock_t start, end;
// Mutex 테스트
pthread_mutex_init(&mutex, NULL);
start = clock();
for (int i = 0; i < NUM_THREADS; i++) {
pthread_create(&threads[i], NULL, mutex_test, NULL);
}
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
end = clock();
printf("Mutex Time: %lf seconds\n", (double)(end - start) / CLOCKS_PER_SEC);
pthread_mutex_destroy(&mutex);
// RWLock 테스트
pthread_rwlock_init(&rwlock, NULL);
start = clock();
for (int i = 0; i < NUM_THREADS; i++) {
pthread_create(&threads[i], NULL, rwlock_test, NULL);
}
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
end = clock();
printf("RWLock Time: %lf seconds\n", (double)(end - start) / CLOCKS_PER_SEC);
pthread_rwlock_destroy(&rwlock);
return 0;
}
테스트 결과 (예시)
작업 환경 | Mutex 실행 시간 | RWLock 실행 시간 |
---|---|---|
읽기 90% / 쓰기 10% | 1.2초 | 0.8초 |
읽기 50% / 쓰기 50% | 1.5초 | 1.6초 |
읽기 10% / 쓰기 90% | 1.8초 | 2.0초 |
결론
- 읽기 작업이 많은 환경
pthread_rwlock
이 읽기 락을 통해 동시성을 제공하므로 성능이 더 뛰어납니다. - 쓰기 작업이 많은 환경
Mutex가 단순한 동기화를 제공하므로 더 효율적일 수 있습니다. - 균형 잡힌 환경
읽기와 쓰기가 비슷한 비율로 발생하는 경우, 두 락의 성능 차이는 크지 않습니다.
작업의 성격에 따라 적절한 락을 선택하는 것이 성능 최적화의 핵심입니다.
pthread_rwlock 사용 시 주의점
pthread_rwlock
은 효율적인 읽기-쓰기 동기화를 제공하지만, 잘못된 사용은 성능 저하나 예기치 않은 동작을 유발할 수 있습니다. 이를 방지하기 위해 몇 가지 주의 사항을 알아봅니다.
1. 데드락 발생 방지
락을 획득한 후 해제하지 않으면 다른 스레드가 영구적으로 대기 상태에 빠질 수 있습니다. 특히 복잡한 프로그램에서 데드락이 발생하지 않도록 주의해야 합니다.
예방 방법
- 항상 락을 획득한 후 반드시 해제하도록 설계합니다.
- 락 해제를 잊지 않도록 함수에서 락과 해제를 포함한 구조를 사용합니다.
pthread_rwlock_rdlock(&rwlock);
// 작업 수행
pthread_rwlock_unlock(&rwlock); // 반드시 호출
2. 우선순위 반전 문제
pthread_rwlock
은 기본적으로 읽기 락을 선호하는 경향이 있어 쓰기 작업이 무한정 대기 상태에 빠질 수 있습니다. 이를 우선순위 반전 문제라고 합니다.
예방 방법
- 쓰기 락이 필요한 작업이 장시간 대기하지 않도록 설계합니다.
- POSIX 확장 속성(
pthread_rwlockattr_setkind_np
)을 사용하여 락의 선호도를 조정합니다.
pthread_rwlockattr_t attr;
pthread_rwlockattr_init(&attr);
pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP);
pthread_rwlock_init(&rwlock, &attr);
pthread_rwlockattr_destroy(&attr);
3. 스레드 안전 보장
모든 스레드가 pthread_rwlock
을 올바르게 사용할 수 있도록 설계해야 합니다.
주의 사항
- 동일한 락 객체를 여러 스레드에서 사용해야 합니다.
- 동일한 스레드가 중첩적으로 락을 획득하려고 하면 데드락이 발생할 수 있습니다.
pthread_rwlock_rdlock(&rwlock);
// 중복 락 획득 (위험)
pthread_rwlock_rdlock(&rwlock); // 데드락 발생 가능
4. 성능 저하 가능성
pthread_rwlock
은 읽기와 쓰기 작업을 분리하여 효율적이지만, 쓰기 작업이 많거나 읽기-쓰기 작업이 불균형한 경우 성능이 저하될 수 있습니다.
예방 방법
- 읽기 작업이 대부분인 경우
pthread_rwlock
을 사용합니다. - 쓰기 작업 비율이 높으면 Mutex를 사용하는 것이 효율적일 수 있습니다.
5. 락 초기화와 해제
초기화되지 않은 락을 사용하거나 이미 해제된 락을 사용하는 것은 정의되지 않은 동작을 유발합니다.
예방 방법
- 반드시
pthread_rwlock_init
으로 초기화한 후 사용합니다. - 사용이 끝난 후에는
pthread_rwlock_destroy
를 호출하여 자원을 정리합니다.
6. 락 대기 시간 관리
대기 시간이 길어지면 프로그램 성능에 악영향을 미칠 수 있습니다.
예방 방법
- 필요 시 타임아웃이 있는 함수(
pthread_rwlock_timedrdlock
,pthread_rwlock_timedwrlock
)를 사용하여 대기 시간을 제한합니다.
정리
pthread_rwlock
은 멀티스레드 환경에서 효율적인 읽기-쓰기 동기화를 제공하지만, 올바른 사용이 필수적입니다. 위의 주의 사항을 고려하여 프로그램을 설계하면 데이터 무결성과 성능을 모두 확보할 수 있습니다.
요약
pthread_rwlock
은 C언어에서 멀티스레드 환경에서 읽기와 쓰기 작업을 효율적으로 동기화하는 데 유용한 도구입니다. 본 기사에서는 읽기-쓰기 동기화의 개념, pthread_rwlock
의 초기화와 해제, 읽기 락과 쓰기 락의 사용법, 성능 비교, 그리고 사용 시 주의사항을 다뤘습니다.
효율적인 락 관리는 데이터 무결성을 보장하고 프로그램 성능을 최적화하는 데 핵심적인 역할을 합니다. 읽기 작업이 많은 경우 pthread_rwlock
을 사용하여 병렬성을 극대화하고, 주의사항을 고려하여 안정적인 멀티스레드 프로그램을 설계하세요.