스레드 안전
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.
1. 개요
스레드 안전은 여러 스레드가 동시에 접근해도 데이터의 일관성을 유지하는 코드의 속성을 의미한다. 스레드 안전성 판단 기준에는 전역 변수, 힙, 파일과 같은 공유 자원 사용 여부, 포인터를 통한 데이터 간접 접근, 부수 효과 발생 여부 등이 있다. 스레드 안전성을 구현하기 위해 재진입성, 상호 배제, 스레드 지역 저장소, 원자 연산 등의 기법을 사용하며, 공유 상태 회피와 동기화 기법을 결합하여 구현할 수 있다. 스레드 안전성은 스레드 안전하지 않음, 스레드 안전(직렬화), 스레드 안전(MT-안전)의 세 가지 수준으로 분류되며, 소프트웨어 라이브러리는 특정 스레드 안전성 보장을 제공할 수 있다. 스레드 안전성 보장은 데드락 위험을 예방하거나 제한하는 설계 단계를 포함하지만, 데드락 발생을 항상 보장할 수는 없다. 자바의 `synchronized` 키워드나 C/C++의 뮤텍스, 원자 연산 등을 사용하여 스레드 안전한 코드를 구현할 수 있다.
더 읽어볼만한 페이지
- 스레드 - 멀티스레딩
멀티스레딩은 프로세스 내에서 여러 스레드를 동시 실행하여 처리 능력을 향상시키는 기술로, 응답성 향상과 자원 공유 등의 장점이 있지만, 자원 간섭과 소프트웨어 복잡성 증가 등의 단점도 존재하며, 다양한 모델과 구현 방식, 스레드 스케줄러, 가상 머신 활성화 가능성 등을 고려해야 한다. - 스레드 - 동시 멀티스레딩
동시 멀티스레딩(SMT)은 슈퍼스칼라 구조 기반으로 한 클럭 사이클 내에 여러 스레드의 명령어를 동시에 실행하여 CPU 자원 활용률을 높이는 기술이지만, 자원 경합, 성능 저하, 보안 취약점 등의 단점도 있으며, 인텔 하이퍼 스레딩이 대표적이다. - 프로그래밍 언어 주제 - 프로그래밍 패러다임
프로그래밍 패러다임은 프로그래머에게 프로그램 작성 방식을 제시하는 관점 또는 스타일이며, 구조적 프로그래밍을 시작으로 절차적, 객체 지향, 선언형 등으로 발전해 명령형, 선언형, 멀티 패러다임 프로그래밍 언어로 분류된다. - 프로그래밍 언어 주제 - 프로그램 최적화
프로그램 최적화는 컴퓨터 프로그램의 효율성을 높이는 과정으로, 더 효율적인 구현 방식을 선택하거나 불필요한 기능을 제거하여 문제 해결에 집중하며, 다양한 수준에서 플랫폼 의존적이거나 독립적인 기술을 활용하여 수행되지만, 특정한 품질 지표를 개선하기 위해 다른 측면을 희생하는 트레이드오프가 발생할 수 있다.
| 스레드 안전 | |
|---|---|
| 스레드 안전성 | |
| 정의 | 다중 스레드 환경에서 여러 스레드가 동시에 특정 코드 영역에 접근하더라도 프로그램의 정확성을 보장하는 속성이다. |
| 설명 | 스레드 안전한 코드는 경쟁 조건이나 데이터 레이스와 같은 문제를 일으키지 않으며, 여러 스레드가 동시에 실행되더라도 예측 가능한 결과를 제공한다. |
| 중요성 | 병렬 프로그래밍에서 안정성과 신뢰성을 확보하는 데 필수적이다. 여러 스레드가 공유 자원에 안전하게 접근하고 수정할 수 있도록 보장한다. |
| 스레드 안전성 확보 방법 | |
| 동기화 기법 | 뮤텍스: 공유 자원에 대한 접근을 상호 배타적으로 제어한다. 세마포어: 제한된 수의 스레드만 공유 자원에 접근하도록 허용한다. 모니터: 공유 자원과 해당 자원에 대한 접근을 제어하는 코드를 캡슐화한다. 스핀락: 짧은 시간 동안 자원을 기다리는 스레드를 위해 busy-waiting을 사용한다. |
| 원자적 연산 | 더 이상 나눌 수 없는 연산을 사용하여 데이터 레이스를 방지한다. CPU 명령어 수준에서 지원되는 경우가 많다. |
| 스레드 로컬 저장소 | 각 스레드마다 독립적인 변수 복사본을 제공하여 공유 자원에 대한 접근을 제거한다. 스레드 간의 간섭을 방지하고 데이터 무결성을 유지한다. |
| 불변 객체 | 생성 후 상태를 변경할 수 없는 객체를 사용하여 동기화 없이 안전하게 공유할 수 있도록 한다. 함수형 프로그래밍에서 주로 사용된다. |
| 스레드 안전성 문제 발생 예시 | |
| 데이터 레이스 | 여러 스레드가 동시에 공유 자원에 접근하여 하나 이상의 스레드가 값을 변경하려고 할 때 발생한다. |
| 경쟁 조건 | 여러 스레드가 공유 자원에 접근하는 순서에 따라 프로그램의 결과가 달라지는 상황이다. |
| 교착 상태 | 두 개 이상의 스레드가 서로 상대방이 점유한 자원을 기다리면서 무한정 멈추는 상태이다. |
| 라이브락 | 스레드가 계속해서 서로 양보하지만, 실질적인 진행은 이루어지지 않는 상황이다. |
| 스레드 안전성 관련 용어 | |
| 재진입 가능 (Reentrant) | 함수가 자기 자신을 호출하는 경우에도 안전하게 동작할 수 있는 속성이다. |
| 스레드-한정 데이터 (Thread-local storage, TLS) | 각 스레드마다 고유한 데이터 영역을 제공하여 데이터 격리를 보장한다. |
| 기타 | |
| 주의사항 | 스레드 안전성을 확보하는 것은 복잡하고 오류가 발생하기 쉬운 작업이 될 수 있다. 신중한 설계, 코드 검토, 테스트를 통해 스레드 안전성을 검증해야 한다. 정적 분석 도구는 스레드 안전성 문제를 식별하는 데 도움이 될 수 있다. |
2. 스레드 안전성 판단 기준
전역 변수나 힙, 파일과 같이 여러 스레드가 동시에 접근 가능한 자원을 사용하는지, 핸들과 포인터를 통한 데이터의 간접 접근 여부, 부수 효과를 가져오는 코드가 있는지 등을 고려하여 스레드 안전성을 판단할 수 있다.[3]
다음은 스레드 안전성 판단 시 고려해야 할 사항이다.
- 전역 변수 등의 정적 기억 영역 기간을 갖는 변수나, 동적 메모리 할당된 힙 영역에 접근하고 있는지 여부
- 전역적인 제한이 있는 리소스 (파일, 프로세스 등)를 확보/해제하고 있는지 여부
- 참조나 포인터에 의한 간접 접근을 하고 있는지 여부
- 명확한 부작용이 있는지 여부 (예: C 언어에서 volatile 변수에 접근하는 등)
스택상의 변수만 사용하고, 인수에만 의존하며, 유사한 특성의 서브루틴만 호출한다면, 그 서브루틴은 재진입 가능하며 스레드 안전하다. 이러한 서브루틴은 "순수 함수" 등으로 불리기도 하며, 수학의 함수와 매우 유사하다.
정적 코드 분석 도구 중에는 프로그램의 병행성에 관한 버그를 어느 정도 검출해 주는 것도 있으며, 스레드 안전하지 않은 코드에 대해 경고를 한다.[11]
3. 스레드 안전성 구현 방법
스레드 안전성을 확보하는 방법에는 크게 공유 상태를 회피하는 방법과 동기화 기법을 사용하는 방법이 있다.
- 공유 상태 회피:
- 재진입: 여러 스레드에서 동시에 함수를 호출해도 올바른 결과가 나오도록 코드를 작성한다.
- 스레드 로컬 스토리지: 각 스레드에 별도 저장소를 사용하여 공유 자원 접근을 최소화한다.
- 불변 객체: 객체 생성 후에는 상태를 변경할 수 없게 하여 읽기 전용 데이터만 공유한다.
이러한 방법들을 결합하여 사용하기도 하는데, 공유 데이터의 스레드 고유 복사본을 만들고 이 복사본을 통해 공유 데이터를 원자적으로 업데이트하는 방식이 일반적이다. 이를 통해 코드 대부분은 병렬로 실행하고, 꼭 필요한 부분만 직렬화할 수 있다.
파일과 같은 시스템 공유 리소스는 같은 프로세스 내 다른 스레드뿐 아니라 다른 프로세스의 스레드도 접근할 수 있다. 따라서 스레드 안전성 확보만으로는 부족할 수 있고, 필요하면 프로세스 간 배타 제어(파일 락 참조)도 해야 한다.
3. 1. 공유 상태 회피
재진입(서브루틴)[6]: 스레드가 부분적으로 실행하고, 동일한 스레드에서 실행하거나, 다른 스레드에서 동시에 실행하더라도 원래 실행을 올바르게 완료할 수 있도록 코드를 작성한다. 이렇게 하려면 상태 정보를 각 실행에 로컬인 변수(일반적으로 스택에)에 저장해야 하며, 정적 또는 전역 변수나 다른 비 로컬 상태에 저장하면 안 된다. 모든 비 로컬 상태는 원자적 연산을 통해 접근해야 하며 데이터 구조도 재진입 가능해야 한다.스레드 로컬 스토리지: 변수를 지역화하여 각 스레드가 자체적인 개인 복사본을 갖도록 한다. 이러한 변수는 서브루틴 및 기타 코드 경계에서 값을 유지하며, 해당 변수에 접근하는 코드가 다른 스레드에서 동시에 실행될 수 있음에도 불구하고 각 스레드에 로컬이기 때문에 스레드 안전하다.
불변 객체: 객체의 상태는 생성 후 변경할 수 없다. 이는 읽기 전용 데이터만 공유되며 본질적인 스레드 안전성이 확보됨을 의미한다. 그런 다음 가변(const가 아닌) 연산은 기존 객체를 수정하는 대신 새 객체를 생성하는 방식으로 구현할 수 있다. 이 접근 방식은 함수형 프로그래밍의 특징이며 Java, C#, 및 Python의 ''string'' 구현에서도 사용된다. ( 불변 객체 참조.)
3. 2. 동기화 기법
- '''재진입성''': 어떤 함수가 한 스레드에 의해 호출되어 실행 중일 때, 다른 스레드가 그 함수를 호출하더라도 그 결과가 각각에게 올바로 주어져야 한다.[6] 이를 위해 각 실행에 로컬인 변수에 상태 정보를 저장하고, 정적 또는 전역 변수나 다른 비 로컬 상태를 사용하지 않도록 코드를 작성한다.
- '''상호 배제''': 공유 자원을 꼭 사용해야 할 경우 해당 자원의 접근을 세마포어 등의 락으로 통제한다.[10] 교착 상태, 라이브락, 자원 기아와 같은 부작용을 초래할 수 있으므로 신중하게 사용해야 한다.
- '''스레드 지역 저장소''': 공유 자원의 사용을 최대한 줄여 각각의 스레드에서만 접근 가능한 저장소들을 사용함으로써 동시 접근을 막는다. 각 스레드는 자체적인 개인 변수 복사본을 가지며, 이 변수들은 서브루틴 및 기타 코드 경계에서 값을 유지한다.
- '''원자 연산''': 공유 자원에 접근할 때 원자적 연산을 이용하거나 '원자적'으로 정의된 접근 방법을 사용함으로써 상호 배제를 구현할 수 있다. 원자적 연산은 다른 스레드에 의해 중단될 수 없으며, 런타임 라이브러리에서 제공하는 특수 기계어 명령어를 사용한다.
- '''불변 객체''': 객체의 상태를 생성 후 변경할 수 없도록 하여 읽기 전용 데이터만 공유되도록 한다. 가변 연산은 기존 객체를 수정하는 대신 새 객체를 생성하는 방식으로 구현할 수 있다.
4. 스레드 안전성 수준
다양한 공급업체들은 스레드 안전성에 대해 약간씩 다른 용어를 사용하지만,[3] 가장 일반적으로 사용되는 스레드 안전성 용어는 다음과 같다.[4]
- '''스레드 안전하지 않음''': 데이터 구조는 서로 다른 스레드에서 동시에 접근해서는 안 된다.
- '''스레드 안전, 직렬화''': 여러 스레드에서 동시에 해당 자원에 접근할 때 경쟁 조건이 발생하지 않도록 '''모든 자원에 대해 단일 뮤텍스'''를 사용한다.
- '''스레드 안전, MT-안전''': 여러 스레드에서 동시에 해당 자원에 접근할 때 경쟁 조건이 발생하지 않도록 '''모든 개별 자원에 대해 뮤텍스'''를 사용한다.
5. 스레드 안전성과 교착 상태
스레드 안전성 보장은 일반적으로 다양한 형태의 데드락의 위험을 예방하거나 제한하기 위한 설계 단계를 포함하며, 동시 성능을 극대화하기 위한 최적화도 포함한다.[4] 그러나 데드락은 콜백과 라이브러리 자체와 무관한 아키텍처 계층 위반으로 인해 발생할 수 있으므로 데드락이 발생하지 않음을 항상 보장할 수는 없다.
6. 예제
자바와 C, C++11를 이용한 스레드 안전 구현 예시를 살펴본다.
자바에서는 `synchronized` 키워드를 사용하여 메서드를 스레드 안전하게 만들 수 있다. C에서는 각 스레드가 자체 스택을 가지지만, 정적 변수는 모든 스레드가 공유한다. 따라서 여러 스레드가 동시에 정적 변수에 접근하면 경합 조건이 발생할 수 있다. 이를 방지하기 위해 락 또는 뮤텍스('''mut'''ual '''ex'''clusion, 상호 배제)를 사용할 수 있다.
다음은 뮤텍스를 사용하여 스레드 안전하지만 재진입 가능하지 않은 C 코드 예시이다.
```c
# include
int increment_counter ()
{
static int counter = 0;
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// 한 번에 하나의 스레드만 증가 허용
pthread_mutex_lock(&mutex);
++counter;
// 다른 스레드가 추가로 증가시키기 전에 값을 저장
int result = counter;
pthread_mutex_unlock(&mutex);
return result;
}
```
위 코드에서 뮤텍스는 공유 `counter` 변수에 대한 접근을 동기화하여 스레드 안전성을 확보한다. 그러나 이 함수가 재진입 가능한 인터럽트 핸들러에서 사용될 경우, 뮤텍스가 잠긴 상태에서 두 번째 인터럽트가 발생하면 교착 상태에 빠질 수 있다.
6. 1. 자바 예제
java
class Counter {
private int i = 0;
public synchronized void inc() {
i++;
}
}
```
자바 코드에서, 자바 키워드 synchronized는 메서드를 스레드 안전하게 만든다.[1]
6. 2. C/C++ 예제
cpp
#include
int increment_counter()
{
static std::atomic
// 증가는 원자적으로 수행되도록 보장
int result = ++counter;
return result;
}
```
위의 C++11 코드는 락 프리(원자적 연산)를 사용하여 스레드 안전하고 재진입 가능하도록 구현되었다. 뮤텍스(mutex)를 사용한 이전 C 예제와 달리, `std::atomic
이 예제에서 `++counter` 연산은 원자적으로 수행되므로, 여러 스레드가 동시에 `increment_counter` 함수를 호출해도 `counter` 값은 정확하게 증가한다. 또한, 락을 사용하지 않기 때문에 재진입성 문제도 발생하지 않는다.[1]
참조
[1]
서적
The Linux Programing Interface
No Starch Press
[2]
웹사이트
Multithreaded Programming Guide: Chapter 7 Safe and Unsafe Interfaces
https://docs.oracle.[...]
Oracle Corporation
2024-04-30
[3]
웹사이트
API thread safety classifications
https://www.ibm.com/[...]
IBM
2023-10-09
[4]
웹사이트
Oracle: Thread safety
https://docs.oracle.[...]
Docs.oracle.com
2013-10-16
[5]
웹사이트
MT Safety Levels for Libraries
https://docs.oracle.[...]
2024-05-17
[6]
웹사이트
Reentrancy and Thread-Safety | Qt 5.6
https://doc.qt.io/qt[...]
Qt Project
2016-04-20
[7]
웹사이트
スレッドセーフとは - 意味をわかりやすく - IT用語辞典 e-Words
https://e-words.jp/w[...]
[8]
웹사이트
CON00-C. 複数のスレッドによる競合状態を避ける
https://www.jpcert.o[...]
[9]
서적
POSIX threads - Mastering C++ Multithreading [Book]
https://www.oreilly.[...]
[10]
뉴스
第3回 マルチスレッドでデータの不整合を防ぐための排他制御 ― マルチスレッド・プログラミングにおける排他制御と同期制御(前編) ―:連載.NETマルチスレッド・プログラミング入門(2/3 ページ) - @IT
https://atmarkit.itm[...]
[11]
웹사이트
アプリケーション開発で質の高いコードレビューを実現するためのポイントとは ? ~ 後編~ - builders.flash☆ - 変化を求めるデベロッパーを応援するウェブマガジン | AWS
https://aws.amazon.c[...]
본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.
문의하기 : help@durumis.com