스레드 세이프는 무엇을 의미합니까?


124

최근에 UI 스레드가 아닌 스레드에서 텍스트 상자에 액세스하려고했지만 예외가 발생했습니다. "코드가 스레드로부터 안전하지 않다"는 내용이 나와서 결국 위임 (MSDN의 샘플이 도움이 됨)을 작성하고 대신 호출했습니다.

하지만 그래도 모든 추가 코드가 필요한 이유를 이해하지 못했습니다.

업데이트 : 확인하면 심각한 문제가 발생합니까?

Controls.CheckForIllegalCrossThread..blah =true

5
일반적으로 "스레드 안전"이란 용어를 사용하는 사람이 적어도 그 사람에게 의미한다고 생각하는 모든 것을 의미합니다. 따라서 이는 매우 유용한 언어 구조가 아닙니다. 스레드 코드의 동작에 대해 이야기 할 때 훨씬 더 구체적이어야합니다.


죄송합니다 @ 데이브 나는 ... 어쨌든 ... 덕분에 검색을 시도했지만 포기
비벡 버나드

1
발생하지 않는 코드Race-Condition
Muhammad Babar

답변:


121

Eric Lippert"스레드 안전"이라고 부르는 것은 무엇입니까? Wikipedia에서 찾은 스레드 안전성의 정의에 대해.

링크에서 추출한 3 가지 중요한 사항 :

"여러 스레드에서 동시에 실행하는 동안 올바르게 작동하는 코드는 스레드로부터 안전합니다."

"특히 동일한 공유 데이터에 액세스하기 위해 여러 스레드에 대한 요구를 충족해야합니다.…"

"… 그리고 주어진 시간에 단 하나의 스레드에서만 액세스 할 수있는 공유 데이터의 필요성."

읽을만한 가치가 있습니다!


25
링크 전용 답변은 향후 언제든지 나빠질 수 있으므로 피해주세요.
akhil_mittal 2015-08-23


106

가장 간단한 용어로 threadsafe는 여러 스레드에서 액세스하는 것이 안전함을 의미합니다. 프로그램에서 여러 스레드를 사용하고 각각 공통 데이터 구조 또는 메모리의 위치에 액세스하려고 할 때 여러 가지 나쁜 일이 발생할 수 있습니다. 따라서 이러한 나쁜 일을 방지하기 위해 추가 코드를 추가합니다. 예를 들어, 두 사람이 동시에 같은 문서를 쓰고 있다면 두 번째로 저장할 사람이 첫 번째 사람의 작업을 덮어 씁니다. 스레드를 안전하게 만들려면 사람 2가 문서를 편집 할 수 있도록 허용하기 전에 사람 1이 작업을 완료 할 때까지 기다려야합니다.


11
이를 동기화라고합니다. 권리?
JavaTechnical

3
예. 다양한 스레드가 공유 리소스에 대한 액세스를 기다리도록 강제하는 것은 동기화를 통해 수행 할 수 있습니다.
Vincent Ramdhanie 2014

Gregory의 승인 된 답변에서 그는 "여러 스레드에 의한 동시 실행 중에 올바르게 작동하는 코드 조각은 스레드로부터 안전합니다."라고 말합니다. "그런 다음 스레드를 안전하게 만들려면 사람 1을 기다리도록 강요해야합니다."라고 말하는 동안 그는 동시가 허용되지 않는다고 말하지 않습니까? 설명해 주시겠습니까?
Honey

그건 같은거야. 코드 스레드를 안전하게 만드는 예로서 간단한 메커니즘을 제안하고 있습니다. 사용 된 메커니즘에 관계없이 동일한 코드를 실행하는 여러 스레드가 서로 간섭해서는 안됩니다.
Vincent Ramdhanie

그렇다면 이것은 전역 및 정적 변수를 사용하는 코드에만 적용됩니까? 문서를 편집하는 사람의 예를 사용하여 사람 2가 다른 문서에서 문서 작성 코드를 실행하는 것을 방지하는 것은 의미가 없다고 생각합니다.
아론 프랑케

18

위키 백과 에는 스레드 안전성에 대한 기사가 있습니다.

정의 페이지 (광고를 건너 뛰어야합니다. 죄송합니다)는 다음과 같이 정의합니다.

컴퓨터 프로그래밍에서 스레드 안전은 스레드 간의 원치 않는 상호 작용없이 여러 프로그래밍 스레드에서 호출 할 수있는 프로그램 부분 또는 루틴을 나타냅니다.

스레드는 프로그램의 실행 경로입니다. 단일 스레드 프로그램에는 스레드가 하나만 있으므로이 문제가 발생하지 않습니다. 사실상 모든 GUI 프로그램에는 다중 실행 경로가 있으므로 스레드가 두 개 이상 있습니다. 하나는 GUI 표시를 처리하고 사용자 입력을 처리하기위한 것이고 다른 하나는 실제로 프로그램 작업을 수행하기위한 것입니다.

이는 프로그램이 작동하는 동안 UI가 계속 응답하도록하기 위해 장기 실행 프로세스를 비 UI 스레드로 오프로드하여 수행합니다. 이러한 스레드는 한 번 생성되어 프로그램의 수명 동안 존재하거나 필요할 때 생성되고 완료되면 파괴 될 수 있습니다.

이러한 스레드는 종종 디스크 I / O, 화면에 결과 출력 등과 같은 일반적인 작업을 수행해야하기 때문에 이러한 코드 부분은 여러 스레드에서 호출되는 것을 처리 할 수있는 방식으로 작성되어야합니다. 동시. 여기에는 다음과 같은 내용이 포함됩니다.

  • 데이터 사본 작업
  • 중요한 코드 주위에 잠금 추가

8

간단히 말해 스레드 안전이란 문제가 발생하지 않고 동시에 여러 스레드에서 메서드 또는 클래스 인스턴스를 사용할 수 있음을 의미합니다.

다음 방법을 고려하십시오.

private int myInt = 0;
public int AddOne()
{
    int tmp = myInt;
    tmp = tmp + 1;
    myInt = tmp;
    return tmp;
}

이제 스레드 A와 스레드 B는 모두 AddOne ()을 실행하려고합니다. 그러나 A가 먼저 시작하고 myInt (0)의 값을 tmp로 읽습니다. 이제 어떤 이유로 스케줄러는 스레드 A를 중지하고 스레드 B로 실행을 연기하기로 결정합니다. 스레드 B는 이제 myInt 값 (여전히 0)을 자체 변수 tmp로 읽어들입니다. 스레드 B는 전체 메서드를 완료하므로 결국 myInt = 1이 반환됩니다. 이제 스레드 A의 차례입니다. 스레드 A는 계속됩니다. 그리고 tmp에 1을 추가합니다 (tmp는 스레드 A의 경우 0 임). 그리고이 값을 myInt에 저장합니다. myInt는 다시 1입니다.

따라서이 경우 AddOne 메서드가 두 번 호출되었지만 메서드가 스레드 안전 방식으로 구현되지 않았기 때문에 myInt 값은 예상대로 2가 아니라 첫 번째 스레드가 완료되기 전에 두 번째 스레드가 변수 myInt를 읽었으므로 1입니다. 업데이트.

사소하지 않은 경우 스레드로부터 안전한 메서드를 만드는 것은 매우 어렵습니다. 그리고 꽤 많은 기술이 있습니다. Java에서는 메서드를 동기화 된 것으로 표시 할 수 있습니다. 즉, 주어진 시간에 하나의 스레드 만 해당 메서드를 실행할 수 있습니다. 다른 스레드는 줄을 서서 기다립니다. 이렇게하면 메서드 스레드가 안전 해지지 만 메서드에서 수행 할 작업이 많으면 많은 공간이 낭비됩니다. 또 다른 기술은 '메서드의 작은 부분 만 동기화 된 것으로 표시'하는 것입니다.잠금 또는 세마포어를 만들고이 작은 부분을 잠그면 (일반적으로 중요 섹션이라고 함). 잠금없는 스레드 안전으로 구현 된 메서드도 있습니다. 즉, 여러 스레드가 문제를 일으키지 않고 동시에 경쟁 할 수있는 방식으로 빌드되었습니다. 하나의 원자 호출을 실행합니다. 원자 적 호출은 중단 할 수없고 한 번에 하나의 스레드에서만 수행 할 수있는 호출입니다.


AddOne 메서드가 두 번 호출 된 경우
Sujith PS 2014

6

평신도에 대한 실제 사례는

인터넷과 모바일 뱅킹이 가능한 은행 계좌가 있고 계좌에 $ 10 만 있다고 가정 해 보겠습니다. 모바일 뱅킹을 사용하여 다른 계좌로 잔액을 이체하고 그 동안 동일한 은행 계좌로 온라인 쇼핑을했습니다. 이 은행 계좌가 스레드 세이프가 아닌 경우 은행은 동시에 두 가지 거래를 수행 할 수 있도록 허용하고 은행은 파산합니다.

Threadsafe는 여러 스레드가 동시에 객체에 액세스하려고해도 객체의 상태가 변경되지 않음을 의미합니다.


5

"Java Concurrency in Practice"책에서 더 많은 설명을 얻을 수 있습니다.

런타임 환경에 의한 스레드 실행의 예약 또는 인터리빙에 관계없이 여러 스레드에서 액세스 할 때 클래스가 올바르게 작동하고 호출 코드 부분에서 추가 동기화 또는 기타 조정없이 클래스가 스레드로부터 안전합니다.


4

모듈은 다중 스레드 및 동시 사용에 직면하여 불변을 유지할 수 있음을 보장하는 경우 스레드로부터 안전합니다.

여기서 모듈은 데이터 구조, 클래스, 객체, 메서드 / 프로 시저 또는 함수일 수 있습니다. 기본적으로 범위가 지정된 코드 및 관련 데이터입니다.

보증은 잠재적으로 특정 CPU 아키텍처와 같은 특정 환경으로 제한 될 수 있지만 이러한 환경에서는 유지되어야합니다. 환경에 대한 명시적인 구분이 없으면 일반적으로 코드가 컴파일되고 실행될 수있는 모든 환경에 대해 유지된다는 것을 암시하는 것으로 간주됩니다.

스레드에 안전하지 않은 모듈 다중 스레드 및 동시 사용에서 올바르게 작동 할 수 있지만 이는 신중한 설계보다 운과 우연에 더 가깝습니다. 일부 모듈이 아래로 파손되지 않더라도 다른 환경으로 이동하면 파손될 수 있습니다.

멀티 스레딩 버그는 종종 디버그하기 어렵습니다. 그들 중 일부는 가끔 발생하는 반면 다른 일부는 공격적으로 나타납니다.이 역시 환경에 따라 다를 수 있습니다. 미묘하게 잘못된 결과 또는 교착 상태로 나타날 수 있습니다. 예측할 수없는 방식으로 데이터 구조를 엉망으로 만들 수 있으며, 코드의 다른 원격 부분에 불가능 해 보이는 다른 버그가 나타날 수 있습니다. 매우 응용 프로그램에 따라 다를 수 있으므로 일반적인 설명을 제공하기가 어렵습니다.


3

스레드 안전 : 스레드 안전 프로그램은 메모리 일관성 오류로부터 데이터를 보호합니다. 다중 스레드 프로그램에서 스레드 안전 프로그램은 동일한 개체의 여러 스레드에서 여러 읽기 / 쓰기 작업으로 인한 부작용을 일으키지 않습니다. 다른 스레드는 일관성 오류없이 객체 데이터를 공유하고 수정할 수 있습니다.

고급 동시성 API를 사용하여 스레드 안전성을 달성 할 수 있습니다. 이 문서 페이지 는 스레드 안전성을 달성하기위한 좋은 프로그래밍 구조를 제공합니다.

Lock Objects 는 많은 동시 응용 프로그램을 단순화하는 잠금 관용구를 지원합니다.

실행 자는 스레드를 시작하고 관리하기위한 고급 API를 정의합니다. java.util.concurrent에서 제공하는 Executor 구현은 대규모 애플리케이션에 적합한 스레드 풀 관리를 제공합니다.

동시 컬렉션을 사용하면 대규모 데이터 컬렉션을보다 쉽게 ​​관리 할 수 ​​있으며 동기화의 필요성을 크게 줄일 수 있습니다.

원자 변수 에는 동기화를 최소화하고 메모리 일관성 오류를 방지하는 기능이 있습니다.

ThreadLocalRandom (JDK 7에서)은 여러 스레드에서 의사 난수를 효율적으로 생성합니다.

다른 프로그래밍 구조에 대해서는 java.util.concurrentjava.util.concurrent.atomic 패키지도 참조하십시오 .


1

WinForms 환경에서 분명히 작업하고 있습니다. WinForms 컨트롤은 스레드 선호도를 나타내므로 해당 컨트롤이 생성 된 스레드는 액세스 및 업데이트에 사용할 수있는 유일한 스레드입니다. 그렇기 때문에 MSDN 및 다른 곳에서 호출을 주 스레드로 다시 마샬링하는 방법을 보여주는 예제를 찾을 수 있습니다.

일반적인 WinForms 관행은 모든 UI 작업 전용 단일 스레드를 사용하는 것입니다.


1

http://en.wikipedia.org/wiki/Reentrancy_%28computing%29 의 개념은 일반적으로 메서드가 전역 변수와 같은 부작용을 가지고 있고 이에 의존 할 때 안전하지 않은 스레딩으로 생각하는 것입니다.

예를 들어 부동 소수점 숫자를 문자열로 형식화 한 코드를 보았습니다.이 중 두 개가 다른 스레드에서 실행되면 decimalSeparator의 전역 값이 영구적으로 '.'로 변경 될 수 있습니다.

//built in global set to locale specific value (here a comma)
decimalSeparator = ','

function FormatDot(value : real):
    //save the current decimal character
    temp = decimalSeparator

    //set the global value to be 
    decimalSeparator = '.'

    //format() uses decimalSeparator behind the scenes
    result = format(value)

    //Put the original value back
    decimalSeparator = temp

-2

스레드 안전성을 이해하려면 아래 섹션을 읽으십시오 .

4.3.1. 예 : 위임을 사용하는 차량 추적기

위임의보다 실질적인 예로서 스레드로부터 안전한 클래스에 위임하는 차량 추적기의 버전을 구성 해 보겠습니다. 지도에 위치를 저장하므로 스레드로부터 안전한지도 구현 인 ConcurrentHashMap. 또한 MutablePointListing 4.6에 표시된 것처럼 대신 불변의 Point 클래스를 사용하여 위치를 저장합니다 .

목록 4.6. DelegatingVehicleTracker에서 사용하는 변경 불가능한 Point 클래스입니다.

 class Point{
  public final int x, y;

  public Point() {
        this.x=0; this.y=0;
    }

  public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

}

Point불변이기 때문에 스레드로부터 안전합니다. 변경 불가능한 값은 자유롭게 공유하고 게시 할 수 있으므로 더 이상 반환 할 때 위치를 복사 할 필요가 없습니다.

DelegatingVehicleTracker목록 4.7에서는 명시 적 동기화를 사용하지 않습니다. 상태에 대한 모든 액세스는에서 관리 ConcurrentHashMap하며지도의 모든 키와 값은 변경할 수 없습니다.

목록 4.7. 스레드 안전성을 ConcurrentHashMap에 위임.

  public class DelegatingVehicleTracker {

  private final ConcurrentMap<String, Point> locations;
    private final Map<String, Point> unmodifiableMap;

  public DelegatingVehicleTracker(Map<String, Point> points) {
        this.locations = new ConcurrentHashMap<String, Point>(points);
        this.unmodifiableMap = Collections.unmodifiableMap(locations);
    }

  public Map<String, Point> getLocations(){
        return this.unmodifiableMap; // User cannot update point(x,y) as Point is immutable
    }

  public Point getLocation(String id) {
        return locations.get(id);
    }

  public void setLocation(String id, int x, int y) {
        if(locations.replace(id, new Point(x, y)) == null) {
             throw new IllegalArgumentException("invalid vehicle name: " + id);
        }
    }

}

MutablePointPoint 대신 원래 클래스를 사용했다면 getLocations스레드로부터 안전하지 않은 변경 가능한 상태에 대한 참조를 게시 하도록하여 캡슐화를 깨뜨릴 것 입니다. 차량 추적기 클래스의 동작을 약간 변경했습니다. 모니터 버전은 위치의 스냅 샷을 반환하는 반면 위임 버전은 수정 불가능하지만 차량 위치의 "실시간"보기를 반환합니다. 즉, 스레드 A가 호출 getLocations하고 스레드 B가 나중에 일부 지점의 위치를 ​​수정하면 해당 변경 사항이 스레드 A에 반환 된 맵에 반영됩니다.

4.3.2. 독립 상태 변수

또한 기본 상태 변수가 독립적 인 한 둘 이상의 기본 상태 변수에 스레드 안전성을 위임 할 수 있습니다. 즉, 복합 클래스가 여러 상태 변수를 포함하는 불변성을 부과하지 않습니다.

VisualComponent목록 4.9에서 클라이언트가 마우스 및 키 입력 이벤트에 대한 리스너를 등록 할 수 있도록하는 그래픽 구성 요소입니다. 이벤트가 발생하면 적절한 리스너를 호출 할 수 있도록 각 유형의 등록 된 리스너 목록을 유지합니다. 그러나 마우스 리스너 세트와 키 리스너 간에는 관계가 없습니다. 둘은 독립적이므로 VisualComponent스레드 안전 의무를 두 개의 기본 스레드 안전 목록에 위임 할 수 있습니다.

목록 4.9. 스레드 안전성을 여러 기본 상태 변수에 위임.

public class VisualComponent {
    private final List<KeyListener> keyListeners 
                                        = new CopyOnWriteArrayList<KeyListener>();
    private final List<MouseListener> mouseListeners 
                                        = new CopyOnWriteArrayList<MouseListener>();

  public void addKeyListener(KeyListener listener) {
        keyListeners.add(listener);
    }

  public void addMouseListener(MouseListener listener) {
        mouseListeners.add(listener);
    }

  public void removeKeyListener(KeyListener listener) {
        keyListeners.remove(listener);
    }

  public void removeMouseListener(MouseListener listener) {
        mouseListeners.remove(listener);
    }

}

VisualComponent를 사용하여 CopyOnWriteArrayList각 리스너 목록을 저장합니다. 이것은 리스너 목록 관리에 특히 적합한 스레드로부터 안전한 목록 구현입니다 (섹션 5.2.3 참조). 각 목록은 스레드로부터 안전하며 하나의 상태를 다른 상태에 연결하는 제약 조건이 없기 때문에 VisualComponent스레드 안전 책임을 기본 mouseListenerskeyListeners개체에 위임 할 수 있습니다 .

4.3.3. 위임이 실패 할 때

대부분의 복합 클래스는 VisualComponent구성 요소 상태 변수와 관련된 불변성이 있습니다. NumberRangeListing 4.10에서는 AtomicIntegers상태를 관리하기 위해 2 개 를 사용 하지만 첫 번째 숫자가 두 번째 숫자보다 작거나 같다는 추가 제약을 부과합니다.

목록 4.10. 불변을 충분히 보호하지 않는 숫자 범위 클래스. 이러지마

public class NumberRange {

  // INVARIANT: lower <= upper
    private final AtomicInteger lower = new AtomicInteger(0);
    private final AtomicInteger upper = new AtomicInteger(0);

  public void setLower(int i) {
        //Warning - unsafe check-then-act
        if(i > upper.get()) {
            throw new IllegalArgumentException(
                    "Can't set lower to " + i + " > upper ");
        }
        lower.set(i);
    }

  public void setUpper(int i) {
        //Warning - unsafe check-then-act
        if(i < lower.get()) {
            throw new IllegalArgumentException(
                    "Can't set upper to " + i + " < lower ");
        }
        upper.set(i);
    }

  public boolean isInRange(int i){
        return (i >= lower.get() && i <= upper.get());
    }

}

NumberRange입니다 스레드 안전하지 ; 그것은 하부와 상부를 구속하는 불변성을 보존하지 않습니다. setLowersetUpper방법이 불변을 존중하려고하지만, 그렇게 가난하게. setLower및 둘 다 setUppercheck-then-act 시퀀스이지만 원 자성을 만들기 위해 충분한 잠금을 사용하지 않습니다. 숫자 범위가 (0, 10)이고 한 스레드가을 호출 setLower(5)하는 동안 다른 스레드가를 호출하는 경우 setUpper(4), 불행한 타이밍으로 둘 다 setter의 검사를 통과하고 두 수정 사항이 모두 적용됩니다. 그 결과 범위는 이제 유효하지 않은 상태 인 (5, 4)를 유지 합니다. 따라서 기본 AtomicInteger는 스레드로부터 안전하지만 복합 클래스는 . 기본 상태 변수 lowerupper독립적 NumberRange이지 않으며 단순히 스레드 안전성을 스레드 안전성 상태 변수에 위임 할 수 없습니다.

NumberRange잠금을 사용하여 고정을 유지함으로써 스레드로부터 안전하도록 만들 수 있습니다 (예 : 공통 잠금으로 하단 및 상단 보호). 또한 클라이언트가 불변성을 파괴하는 것을 방지하기 위해 하위 및 상위 게시를 피해야합니다.

클래스에 복합 동작 NumberRange이있는 경우 위임만으로는 스레드 안전성에 적합한 접근 방식이 아닙니다. 이러한 경우 클래스는 전체 복합 동작이 기본 상태 변수에 위임 될 수있는 경우를 제외하고 복합 동작이 원자적임을 보장하기 위해 자체 잠금을 제공해야합니다.

클래스가 여러 개의 독립적 인 스레드 안전 상태 변수로 구성되고 유효하지 않은 상태 전환이있는 작업이없는 경우 스레드 안전을 기본 상태 변수에 위임 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.