답변:
Wikipedia에서 :
스레드 안전성은 멀티 스레드 프로그램의 맥락에서 적용 가능한 컴퓨터 프로그래밍 개념입니다. 여러 스레드가 동시에 실행하는 동안 올바르게 작동하면 코드 조각이 스레드로부터 안전합니다. 특히, 여러 공유 스레드가 동일한 공유 데이터에 액세스 할 필요성과 주어진 시간에 하나의 스레드 만 공유 데이터 조각에 액세스 할 필요성을 충족시켜야합니다.
스레드 안전을 달성하는 몇 가지 방법이 있습니다.
재입국 :
한 작업에서 부분적으로 실행하고 다른 작업에서 다시 입력 한 다음 원래 작업에서 다시 시작할 수 있도록 코드를 작성합니다. 이를 위해서는 정적 또는 전역 변수 대신 각 작업의 로컬 변수, 일반적으로 스택에 상태 정보를 저장해야합니다.
상호 배제 :
공유 데이터에 대한 액세스는 언제든지 하나의 스레드 만 공유 데이터를 읽거나 쓰도록하는 메커니즘을 사용하여 직렬화됩니다. 코드 조각이 여러 공유 데이터 조각에 액세스하는 경우 세심한주의가 필요합니다. 문제에는 경쟁 조건, 교착 상태, 라이브 록, 기아 및 여러 운영 체제 교과서에 열거 된 다양한 기타 문제가 포함됩니다.
스레드 로컬 스토리지 :
변수는 지역화되어 각 스레드마다 고유 한 개인 사본이 있습니다. 이러한 변수는 서브 루틴 및 기타 코드 경계에 걸쳐 값을 유지하며, 액세스하는 코드가 재진입 될 수 있더라도 각 스레드에 대해 로컬이므로 스레드 안전합니다.
원자력 운영 :
다른 스레드에 의해 중단 될 수없는 원자 연산을 사용하여 공유 데이터에 액세스합니다. 일반적으로 런타임 라이브러리에서 사용 가능한 특수 기계 언어 명령어를 사용해야합니다. 작업이 원자 적이기 때문에 공유 데이터는 다른 스레드가 액세스하는 내용에 관계없이 항상 유효한 상태로 유지됩니다. 원자 연산은 많은 스레드 잠금 메커니즘의 기초를 형성합니다.
더 읽기 :
http://en.wikipedia.org/wiki/Thread_safety
프랑스어로 : http://fr.wikipedia.org/wiki/Threadsafe (매우 짧음)
스레드 안전 코드는 많은 스레드가 동시에 실행하더라도 작동하는 코드입니다.
더 유익한 질문은 코드가 스레드에 안전하지 않게 만드는 것입니다. 그리고 정답은 4 가지 조건이 있어야한다는 것입니다 ... 다음 코드를 상상해보십시오 (그리고 기계 언어 번역).
totalRequests = totalRequests + 1
MOV EAX, [totalRequests] // load memory for tot Requests into register
INC EAX // update register
MOV [totalRequests], EAX // store updated value back to memory
나는 Brian Goetz의 Java Concurrency의 정의가 포괄적이라는 점을 좋아합니다.
"클래스는 런타임 환경에 의한 스레드 실행의 스케줄링 또는 인터리빙에 관계없이 호출 스레드의 일부에 대한 추가 동기화 또는 기타 조정없이 여러 스레드에서 액세스 될 때 올바르게 작동하는 경우 스레드로부터 안전합니다. "
다른 사람들이 지적했듯이 스레드 안전은 한 번에 두 개 이상의 스레드가 사용하는 경우 코드가 오류없이 작동한다는 것을 의미합니다.
때때로 컴퓨터 비용과 복잡한 코딩 비용이 발생하기 때문에 항상 바람직하지는 않습니다. 하나의 스레드에서만 클래스를 안전하게 사용할 수 있다면 그렇게하는 것이 좋습니다.
예를 들어, 자바는 거의 동등한 두 개의 클래스를 가지고 StringBuffer
와 StringBuilder
. 차이점은 StringBuffer
스레드로부터 안전하므로 StringBuffer
한 번에 여러 스레드에서 단일 인스턴스를 사용할 수 있다는 것입니다. StringBuilder
스레드로부터 안전하지 않으며 String이 하나의 스레드만으로 빌드 될 때 이러한 경우 (대부분의 경우)를위한 고성능 대체물로 설계되었습니다.
이해하기 쉬운 방법은 코드를 스레드로부터 안전하지 않게 만드는 것입니다. 스레드 응용 프로그램이 원치 않는 동작을하게하는 두 가지 주요 문제가 있습니다.
잠금없이 공유 변수에 액세스이 변수
는 기능을 실행하는 동안 다른 스레드에서 수정할 수 있습니다. 기능의 동작을 확인하기 위해 잠금 메커니즘으로이를 방지하려고합니다. 일반적으로 가장 짧은 시간 동안 잠금을 유지하는 것이 좋습니다.
공유 변수에 대한 상호 종속성으로 인한 교착 상태
두 개의 공유 변수 A와 B가있는 경우 한 함수에서 A를 먼저 잠근 다음 나중에 B를 잠급니다. 다른 함수에서는 B를 잠그고 잠시 후 A를 잠급니다. 는 두 번째 기능이 A 잠금 해제를 기다릴 때 첫 번째 기능이 B 잠금 해제를 기다릴 수있는 잠재적 교착 상태입니다. 이 문제는 개발 환경에서 발생하지 않으며 때때로 만 발생합니다. 이를 방지하려면 모든 잠금 장치는 항상 같은 순서로되어 있어야합니다.
예, 아니오
스레드 안전성은 한 번에 하나의 스레드 만 공유 데이터에 액세스 할 수 있도록하는 것보다 조금 더 중요합니다. 경쟁 조건 , 교착 상태 , 라이브 록 및 리소스 부족을 피하면서 동시에 공유 데이터에 순차적으로 액세스해야 합니다.
여러 스레드가 실행되고 예측할 수없는 결과입니다 하지 스레드 안전 코드의 필요 조건이지만 종종 부산물이다. 예를 들어, 공유 큐, 하나의 생산자 스레드 및 소수의 소비자 스레드 로 생산자-소비자 구성표를 설정할 수 있으며 데이터 흐름을 완벽하게 예측할 수 있습니다. 더 많은 소비자를 소개하기 시작하면 더 무작위로 보이는 결과가 나타납니다.
본질적으로, 다중 스레드 환경에서 많은 것들이 잘못 될 수 있습니다 (명령 순서 변경, 부분적으로 생성 된 객체, CPU 수준에서의 캐싱으로 인해 다른 스레드에서 다른 값을 갖는 동일한 변수 등).
실제로 Java Concurrency에서 제공 한 정의가 마음 에 듭니다 .
[일부 코드]는 런타임 환경에서 해당 스레드의 실행을 예약 또는 인터리빙하고 여러 스레드에서 액세스 할 때 올바르게 동작하는 경우 스레드로부터 안전합니다. 호출 코드.
하여 제대로 그들은 그것의 사양을 준수하는 프로그램 동작합니다을 의미한다.
고안된 예
카운터를 구현한다고 상상해보십시오. 다음과 같은 경우 올바르게 작동한다고 말할 수 있습니다.
counter.next()
이전에 이미 반환 된 값을 반환하지 않습니다 (간단 성을 위해 오버플로 등이 없다고 가정 함)스레드 안전 카운터는 동시에 액세스하는 스레드 수에 관계없이 이러한 규칙에 따라 작동합니다 (일반적으로 순진한 구현에는 해당되지 않음).
참고 : 프로그래머의 교차 게시물
많은 스레드가 동시에이 코드를 실행하는 경우 코드가 제대로 실행됩니다.
다른 좋은 답변 위에 더 많은 정보를 추가하고 싶습니다.
스레드 안전성은 여러 스레드가 메모리 불일치 오류없이 동일한 객체에서 데이터를 쓰거나 읽을 수 있음을 의미합니다. 다중 스레드 프로그램에서 스레드 안전 프로그램은 공유 데이터에 부작용을 일으키지 않습니다 .
자세한 내용은이 SE 질문을 살펴보십시오.
스레드 안전 프로그램은 메모리 일관성을 보장합니다 .
고급 동시 API의 Oracle 문서 페이지 에서 :
메모리 일관성 속성 :
Java ™ 언어 스펙의 17 장은 공유 변수의 읽기 및 쓰기와 같은 메모리 조작에서 발생하는 관계를 정의합니다. 하나의 스레드에 의한 쓰기 결과는 읽기 작업 이전에 쓰기 작업이 발생하는 경우에만 다른 스레드가 읽을 수 있도록 보장됩니다 .
synchronized
및 volatile
구조뿐만 아니라,로 Thread.start()
및 Thread.join()
방법, 캔 형상이 발생하기 전에- 관계.
모든 클래스 java.util.concurrent
와 그 하위 패키지 의 메소드는 이러한 보증을 더 높은 수준의 동기화 로 확장 합니다. 특히:
Runnable
에 제출하기 전에 스레드의 조치 Executor
. 마찬가지로가 제출 된 Callable에 대해서도 마찬가지입니다 ExecutorService
.Future
를 통해 결과를 검색 한 후 발생 하는 조치로 표시됩니다 Future.get()
.Lock.unlock, Semaphore.release, and CountDownLatch.countDown
하기 전의 조치 Lock.lock, Semaphore.acquire, Condition.await, and CountDownLatch.await
.Exchanger
이전 작업 exchange()
은 다른 스레드의 해당 exchange () 이후의 작업보다 먼저 발생합니다.CyclicBarrier.await
및 Phaser.awaitAdvance
그 변형)는 배리어 조치에 의해 수행 된 조치 이전에 발생하고 배리어 조치에 의해 수행 된 조치는 해당 스레드에서 성공적으로 리턴 된 조치 후에 다른 스레드에서 대기합니다.다른 답변을 완료하려면
메소드의 코드가 다음 두 가지 중 하나를 수행 할 때만 동기화가 걱정됩니다.
즉, 메소드 내에 정의 된 변수는 항상 스레드 안전합니다. 메소드에 대한 모든 호출에는 이러한 변수의 자체 버전이 있습니다. 메소드가 다른 스레드 또는 동일한 스레드에 의해 호출되거나 메소드가 자체 호출 (재귀)하는 경우에도 이러한 변수의 값은 공유되지 않습니다.
스레드 스케줄링은 라운드 로빈 일 수 없습니다 . 작업은 동일한 우선 순위의 스레드를 희생하여 CPU를 완전히 비울 수 있습니다. Thread.yield ()를 사용하여 양심을 가질 수 있습니다. (Java에서) Thread.setPriority (Thread.NORM_PRIORITY-1)를 사용하여 스레드의 우선 순위를 낮출 수 있습니다
또한 다음을주의하십시오 :
예를 들어 이것에 대답하자 :
class NonThreadSafe {
private int counter = 0;
public boolean countTo10() {
count = count + 1;
return (count == 10);
}
이 countTo10
메서드는 카운터에 하나를 추가 한 다음 카운트가 10에 도달하면 true를 반환합니다. 한 번만 true를 반환해야합니다.
하나의 스레드 만 코드를 실행하는 한 작동합니다. 두 개의 스레드가 동시에 코드를 실행하면 다양한 문제가 발생할 수 있습니다.
예를 들어, count가 9로 시작하면 한 스레드가 1을 계산하여 계산 (10 만들기) 할 수 있지만 두 번째 스레드가 메소드를 입력하고 1을 다시 추가하여 (11 만들기) 첫 번째 스레드가 10과의 비교를 실행할 수 있습니다. 그런 다음 두 스레드가 비교를 수행하고 count가 11이고 true를 반환하지 않는다는 것을 알게됩니다.
따라서이 코드는 스레드로부터 안전하지 않습니다.
본질적으로 모든 멀티 스레딩 문제는 이러한 종류의 문제의 변형으로 인해 발생합니다.
해결책은 추가 및 비교를 분리 할 수 없도록하는 것입니다 (예를 들어 두 종류의 동기화 코드로 두 명령문을 묶음). 이러한 코드는 스레드로부터 안전합니다.
적어도 C ++에서는 스레드 안전 이 이름에서 많이 벗어나는 점에서 약간 잘못된 것으로 생각합니다. 스레드 안전을 위해 코드는 일반적으로 사전 예방 적 이어야 합니다. 일반적으로 수동적 인 품질이 아닙니다.
클래스가 밟지 않도록하려면 오버 헤드를 추가하는 "추가"기능이 있어야합니다. 이러한 기능은 클래스 구현의 일부이며 일반적으로 인터페이스에 숨겨져 있습니다. 즉, 다른 스레드는 다른 스레드에 의한 동시 액세스와의 충돌에 대해 걱정할 필요없이 클래스 멤버에 액세스 할 수 있으며 평범한 오래된 일반 휴먼 코딩 스타일을 사용하여 매우 게으른 방식으로 그렇게 할 수 있습니다. 이미 호출 된 코드의 내장으로 롤링 된 모든 미친 동기화 작업.
이것이 일부 사람들이 내부적으로 동기화 된 용어를 선호하는 이유 입니다.
내가 만난 이러한 아이디어에 대한 세 가지 주요 용어 세트가 있습니다. 첫 번째이자 역사적으로 더 대중적이지만 더 나쁘다.
두 번째 (그리고 더 나은)는 다음과 같습니다.
세번째는 :
스레드 안전 ~ 스레드 증명 ~ 내부 동기화
의 예 내부적으로 동기화 (일명. 스레드 안전 또는 스레드 증거 ) 시스템 호스트가 문을 열고 인사를 할 수없는 식당이 있습니다. 호스트는 여러 고객을 처리하기위한 레스토랑 메커니즘의 일부이며, 파티 규모를 고려하거나 대기 시간을 고려하는 등 대기중인 고객의 좌석을 최적화하기 위해 다소 까다로운 트릭을 사용할 수 있습니다. 전화로 예약 할 수도 있습니다. 식당은 내부적으로 동기화되어 있으며이 모든 것이 식당과 상호 작용하기위한 인터페이스의 일부이기 때문입니다.
스레드 안전 하지는 않지만 (좋은) ~ 스레드 호환 ~ 외부 동기화 ~ 무료 스레드
은행에 간다고 가정하십시오. 은행원에 대한 경쟁, 즉 경쟁이 있습니다. 당신은 야만인이 아니기 때문에, 자원에 대한 논쟁 중에 행하는 가장 좋은 일은 문명화 된 존재처럼 대기하는 것입니다. 아무도 기술적으로 당신을 그렇게하지 않습니다. 우리는 당신이 스스로 그것을하기 위해 필요한 소셜 프로그래밍이 있기를 바랍니다. 이런 의미에서 은행 로비는 외부 적으로 동기화됩니다. 스레드 안전하지 않다고 말해야합니까? 이것이 thread-safe , thread-unsafe 양극성 용어 집합을 사용하는 경우의 의미입니다 . 아주 좋은 용어는 아닙니다. 더 나은 용어는 외부 적으로 동기화됩니다.은행 로비는 여러 고객이 액세스하는 데 적대적이지 않지만 동기화하는 작업은 수행하지 않습니다. 고객은 스스로 그렇게합니다.
"free threaded"라고도합니다. 여기서 "free"는 "lice from free"또는이 경우에는 lock과 같습니다. 보다 정확하게는 동기화 기본 요소입니다. 그렇다고 코드가 프리미티브없이 여러 스레드에서 실행될 수 있다는 의미는 아닙니다. 그것은 이미 설치되어 있지 않은 것을 의미하며 코드 사용자는 자신에게 적합하지만 직접 설치하는 것은 귀하에게 달려 있습니다. 자체 동기화 프리미티브를 설치하는 것은 어려울 수 있으며 코드에 대해 열심히 생각해야하지만 오늘날의 하이퍼 스레드 CPU에서 프로그램이 실행되는 방식을 사용자 정의 할 수있게함으로써 가능한 가장 빠른 프로그램으로 이어질 수 있습니다.
스레드 안전하지 않음 (및 불량) ~ 스레드 적대적 ~ 동기화 불가능
실 적대적 시스템 의 일상 비유의 예 는 스포츠카가 깜박임을 사용하는 것을 거부하고 레인을 바꾸는 것입니다. 그들의 운전 스타일은 당신이 그들과 조화를 이룰 수 없기 때문에 적대적 이거나 비 동기화 될 수 있습니다. 이것을 방지하십시오. 이 패턴은 반 사회적 으로 더 광범위하게 생각할 수 있는데 , 이는 스레드에 덜 구체적이고 많은 프로그래밍 영역에 더 일반적으로 적용되기 때문에 선호합니다.
첫 번째 및 가장 오래된 용어 집합은 스레드의 적개심 과 스레드 호환성을 더 잘 구분하지 못합니다 . 스레드 호환성은 소위 스레드 안전성보다 수동적이지만 호출 된 코드가 동시 스레드 사용에 안전하지 않다는 것을 의미하지는 않습니다. 이는 내부 구현의 일부로 제공하는 대신이를 허용하여 호출 코드에 적용 할 수있는 동기화에 대해 수동적임을 의미합니다. 스레드 호환 은 대부분의 경우 기본적으로 코드를 작성하는 방법이지만, 이는 기본적 으로 안티 안전 인 것처럼 스레드 안전하지 않은 것으로 잘못 인식되는 경우가 많습니다 . 이는 프로그래머에게 혼란을 일으키는 주요 지점입니다.
참고 : 많은 소프트웨어 설명서는 실제로 "스레드 안전"이라는 용어를 사용하여 "스레드 호환"을 지칭하며 이미 혼란스러워하는 것에 더 많은 혼란을줍니다! 이 이유로 인해 "스레드 안전"및 "스레드 안전하지 않은"이라는 용어를 사용하는 것은 피할 수 없습니다. 일부 소스는 "스레드 안전"이라고 부르는 반면 다른 소스는 동의 할 수 없기 때문에 "스레드 안전하지 않은"이라고 부릅니다. 안전에 대한 몇 가지 추가 표준 (동기화 프리미티브)을 충족해야하는지 또는 "안전한"것으로 간주되기 위해 적대적이지 않아야합니다. 따라서 다른 엔지니어와의 위험한 오해를 피하기 위해 이러한 용어를 사용하지 말고 더 똑똑한 용어를 사용하십시오.
본질적으로, 우리의 목표는 혼돈을 파괴하는 것입니다.
우리는 신뢰할 수있는 결정 론적 시스템을 만들어서 그렇게합니다. 결정 성은 비용이 많이 들며, 대부분 병렬 처리, 파이프 라이닝 및 재정렬에 대한 기회 비용 때문에 발생합니다. 우리는 비용을 낮게 유지하는 데 필요한 결정론의 양을 최소화하려고 노력할뿐만 아니라, 어떤 결정론을 감당할 수 있는지 결정하는 것을 피할 수 있습니다.
스레드 동기화는 순서를 늘리고 혼돈을 줄이는 것입니다. 이를 수행하는 수준은 위에서 언급 한 용어에 해당합니다. 최고 수준은 시스템이 항상 완전히 예측 가능한 방식으로 작동 함을 의미합니다. 두 번째 수준은 호출 코드가 예측할 수없는 가능성을 확실하게 감지 할 수있을 정도로 시스템이 충분히 작동 함을 의미합니다. 예를 들어, 조건 변수 의 가짜 웨이크 업 또는 준비되지 않았기 때문에 뮤텍스를 잠그지 못한 경우. 세 번째 수준은 시스템이 다른 사람과 게임을하기에 충분하지 않으며 혼돈없이 단일 스레드 만 실행할 수 있다는 의미입니다.
대신 생각의 코드 또는 클래스 스레드 안전이나하지, 나는 생각하는 것이 더 도움이된다 생각 행동 thread 세이프되고있다. 임의의 스레딩 컨텍스트에서 실행될 때 지정된대로 동작 할 경우 두 가지 조치는 스레드 안전입니다. 대부분의 경우 클래스는 스레드 안전 방식으로 일부 작업 조합을 지원하고 그렇지 않은 작업을 지원합니다.
예를 들어, 배열 목록 및 해시 세트와 같은 많은 컬렉션은 처음에 하나의 스레드로만 액세스하고 참조가 다른 스레드에 표시 된 후에는 절대 수정되지 않으면 임의의 조합으로 임의의 방식으로 읽을 수 있음을 보장합니다 방해없는 실의.
더 흥미롭게도 .NET의 원래 비 제네릭 컬렉션과 같은 일부 해시 세트 컬렉션은 항목이 제거되지 않는 한 하나의 스레드 만 쓰려고 시도하는 경우 모든 스레드를 컬렉션을 읽는 것은 업데이트가 지연되고 임의의 순서로 발생할 수 있지만 컬렉션이 정상적으로 작동하는 컬렉션에 액세스하는 것처럼 동작합니다. 스레드 # 1이 X를 추가 한 다음 Y를 추가하고 스레드 # 2가 Y를 찾은 다음 X를 보는 경우 스레드 # 2가 Y가 존재하지만 X는 존재하지 않는 것을 볼 수 있습니다. 이러한 동작이 "스레드 안전"인지 여부는 스레드 # 2가 해당 가능성을 처리 할 준비가되었는지 여부에 따라 결정됩니다.
마지막으로, 특히 통신 라이브러리를 차단하는 일부 클래스에는 다른 모든 메소드와 관련하여 스레드로부터 안전한 "클로즈"또는 "폐기"메소드가있을 수 있지만, 스레드에서 안전한 다른 메소드는 없습니다. 서로. 스레드가 블로킹 읽기 요청을 수행하고 프로그램 사용자가 "취소"를 클릭하면 읽기를 수행하려는 스레드가 닫기 요청을 발행 할 방법이 없습니다. 그러나, 닫기 / 삭제 요청은 가능한 빨리 읽기 요청이 취소되도록하는 플래그를 비동기 적으로 설정할 수 있습니다. 스레드에서 닫기가 수행되면 객체가 쓸모 없게되고 향후 작업에 대한 모든 시도가 즉시 실패합니다.