>  기사  >  백엔드 개발  >  C# 멀티스레딩의 스레드 동기화에 대한 자세한 설명(그림 및 텍스트)

C# 멀티스레딩의 스레드 동기화에 대한 자세한 설명(그림 및 텍스트)

黄舟
黄舟원래의
2017-03-28 13:14:542092검색

이 글에서는 주로 C# 스레드 동기화 관련 지식을 소개합니다. 참고할만한 가치가 매우 좋습니다. 아래 에디터로 살펴보겠습니다

멀티스레딩 콘텐츠는 크게 두 부분으로 나누어집니다. 하나는 전용 스레드 풀을 통해 수행할 수 있는 비동기 작업입니다. Task, Parallel, PLINQ 등 여기에는 작업자 스레드 및 IO 스레드가 포함됩니다. 두 번째는 스레드 동기화 문제입니다. 지금 연구하고 있는 것은 스레드 동기화 문제입니다.

"CLR via C#"의 내용을 연구하여 스레드 동기화를 위한 보다 명확한 아키텍처를 구성했습니다. 다중 스레드에서 스레드 동기화를 구현하는 것은 스레드 동기화 구조입니다. 하나는 기본 구조이고 다른 하나는 하이브리드 구조입니다. 소위 기본 요소는 코드에 사용되는 가장 간단한 구성입니다. 기본 구조는 두 가지 범주로 나뉘는데, 하나는 사용자 모드이고 다른 하나는 커널 모드입니다. 하이브리드 구조는 내부적으로 원시 구조를 사용하는 사용자 모드와 커널 모드를 사용하는데, 사용자 모드와 커널 모드는 각각 장단점이 있고, 하이브리드 구조는 장점과 단점의 균형을 맞추기 때문에 모드 사용에 대한 특정 전략이 있습니다. 2. 단점을 방지하도록 설계되었습니다. 다음은 전체 스레드 동기화 아키텍처 목록입니다.

프리미티브

1.1 사용자 모드

1.1.1 휘발성

1.1.2 인터록

1.2 커널 모드

1.2.1 WaitHandle

1.2.2 ManualResetEvent 및 AutoResetEvent

1.2.3 세마포

1.2. Mutex

혼합

2.1 다양한 Slim

2.2 모니터

2.3 MethodImplAttribute 및 동기화 속성

2.4 ReaderWriterLock

2.5 Barier(거의 사용하지 않음)

2.6 CoutdownEvent(거의 사용하지 않음)

메모리에 플라스틱이 있을 때 스레드 동기화 문제의 원인부터 살펴보겠습니다. 변수 A에 저장된 값은 2입니다. 스레드 1이 실행되면 메모리에서 A의 값을 가져와 CPU 레지스터에 저장하고 A의 값을 3에 할당합니다. 이때 , 스레드 1의 값은 다음과 같습니다. 타임 슬라이스가 끝나면 CPU는 스레드 2에 타임 슬라이스를 할당합니다. 스레드 2도 메모리에서 A의 값을 꺼내 메모리에 넣습니다. 변수 A의 새 값 3을 다시 메모리에 넣지 않으면 스레드 2 2는 여전히 이전 값(즉, 더티 데이터) 2를 읽습니다. 그런 다음 스레드 2가 A 값에 대해 일부 판단을 내려야 하는 경우 예상치 못한 결과가 발생합니다. 발생하다.

위의 자원 공유 문제를 해결하기 위해 다양한 방법이 사용되는 경우가 많습니다. 다음은 하나씩 소개하겠습니다

먼저 원시 구조의 사용자 모드에 대해 이야기하겠습니다. 사용자 모드의 장점은 일련의 CPU 명령을 통해 조정되므로 실행이 비교적 빠르다는 것입니다. 이로 인해 발생하는 차단은 매우 짧은 차단 기간 동안만 운영 체제에 관한 한 이 스레드는 항상 실행 중이며 차단된 적이 없습니다. 단점은 시스템 커널만이 그러한 스레드의 실행을 중지할 수 있다는 것입니다. 반면에 스레드가 차단되지 않고 회전하기 때문에 CPU 시간도 차지하게 되어 CPU 시간 낭비가 발생합니다.

첫 번째는 원시 사용자 모드 구조의 휘발성 구조입니다. 이 구조에 대한 인터넷의 많은 이론에서는 CPU가 메모리에서 지정된 필드(필드, 즉 변수)를 읽을 수 있도록 하며, 쓰기는 메모리 쓰기입니다. 그러나 이는 컴파일러의 코드 최적화와 관련이 있습니다. 먼저 다음 코드를 살펴보세요

public class StrageClass
 {
 vo int mFlag = 0;
 int mValue = 0;
 public void Thread1()
 {
  mValue = 5;
  mFlag = 1;
 }
 public void Thread2()
 {
  if (mFlag == 1)
  Console.WriteLine(mValue);
 }
 }

멀티 스레드 동기화 문제를 이해하는 학생은 두 스레드를 사용하여 위의 두 가지 메서드를 각각 실행하면 두 가지 결과가 나온다는 것을 알 수 있습니다.

1. 아무것도 출력하지 마세요;

2.출력 5. 그러나 CSC 컴파일러가 IL 언어로 컴파일하거나 JIT가 기계어로 컴파일되면 코드 최적화가 수행됩니다. Thread1 메소드에서 컴파일러는 두 필드에 값을 할당하는 것이 중요하지 않다고 생각하고 다음에서만 실행됩니다. 단일 스레드 관점에서 볼 때 멀티스레딩 문제를 전혀 고려하지 않으므로 두 줄의 코드 실행 순서가 엉망이 되어 mFlag에 먼저 값 1이 할당될 수 있습니다. , mValue에 5의 값이 할당되어 세 번째 결과로 0이 출력됩니다. 아쉽게도 이 결과를 테스트해 보지는 못했습니다.

이 현상에 대한 해결책은 휘발성 구문입니다. 이 구문을 사용하면 이 구문을 사용하여 필드에서 읽기 작업이 수행될 때마다 해당 작업이 원래 코드 시퀀스에서 먼저 실행된다는 것이 보장됩니다. ; 또는 이 구성을 사용하여 필드에서 쓰기 작업이 수행될 때마다 해당 작업은 원래 코드 시퀀스에서 마지막으로 실행됩니다.

현재 휘발성을 구현하는 구성은 세 가지가 있습니다. 하나는 Thread의 두 가지

정적 메서드 VolatileRead 및 VolatileWrite입니다.

Thread는 필드 값을 읽습니다. . 이 값은 프로세서 수나 프로세서 캐시 상태에 관계없이 컴퓨터 프로세서에 의해 작성된 가장 최근 값입니다.

Thread.VolatileWrite 필드에 즉시 값을 기록하여 해당 값이 컴퓨터의 모든 프로세서에 표시되도록 합니다.

在多处理器系统上, VolatileRead 获得由任何处理器写入的内存位置的最新值。 这可能需要刷新处理器缓存;VolatileWrite 确保写入内存位置的值立即可见的所有处理器。 这可能需要刷新处理器缓存。

即使在单处理器系统上, VolatileRead 和 VolatileWrite 确保值为读取或写入内存,并不缓存 (例如,在处理器寄存器中)。 因此,您可以使用它们可以由另一个线程,或通过硬件更新的字段对访问进行同步。

从上面的文字看不出他和代码优化有任何关联,那接着往下看。

volatile关键字则是volatile构造的另外一种实现方式,它是VolatileRead和VolatileWrite的简化版,使用 volatile 修饰符对字段可以保证对该字段的所有访问都使用 VolatileRead 或 VolatileWrite。MSDN中对volatile关键字的说明是

volatile 关键字指示一个字段可以由多个同时执行的线程修改。 声明为 volatile 的字段不受编译器优化(假定由单个线程访问)的限制。 这样可以确保该字段在任何时间呈现的都是最新的值。

从这里可以看出跟代码优化有关系了。而纵观上面的介绍得出两个结论:

1.使用了volatile构造的字段读写都是直接对内存操作,不涉及CPU寄存器,使得所有线程对它的读写都是同步,不存在脏读了。读操作是原子的,写操作也是原子的。

2.使用了volatile构造修饰(或访问)字段,它会严格按照代码编写的顺序执行,读操作将会在最早执行,写操作将会最迟执行。

最后一个volatile构造是在.NET Framework中新增的,里面包含的方法都是Read和Write,它实际上就相当于Thread的VolatileRead 和VolatileWrite 。这需要拿源码来说明了,随便拿一个Volatile的Read方法来看

而再看看Thraed的VolatileRead方法

另一个用户模式构造是Interlocked,这个构造是保证读和写都是在原子操作里面,这是与上面volatile最大的区别,volatile只能确保单纯的读或者单纯的写。

为何Interlocked是这样,看一下Interlocaked的方法就知道了

Add(ref int,int)// 调用ExternAdd 外部方法
CompareExchange(ref Int32,Int32,Int32)//1与3是否相等,相等则替换2,返回1的原始值
Decrement(ref Int32)//递减并返回 调用add
Exchange(ref Int32,Int32)//将2设置到1并返回
Increment(ref Int32)//自增 调用add

就随便拿其中一个方法Add(ref int,int)来说(Increment和Decrement这两个方法实际上内部调用了Add方法),它会先读到第一个参数的值,在与第二个参数求和后,把结果写到给第一参数中。首先这整个过程是一个原子操作,在这个操作里面既包含了读,也包含了写。至于如何保证这个操作的原子性,估计需要查看Rotor源码才行。在代码优化方面来说,它确保了所有写操作都在Interlocked之前去执行,这保证了Interlocked里面用到的值是最新的;而任何变量的读取都在Interlocked之后读取,这保证了后面用到的值都是最新更改过的。

CompareExchange方法相当重要,虽然Interlocked提供的方法甚少,但基于这个可以扩展出其他更多方法,下面就是个例子,求出两个值的最大值,直接抄了Jeffrey的源码

查看上面代码,在进入循环之前先声明每次循环开始时target的值,在求出最值之后,核对一下target的值是否有变化,如果有变化则需要再记录新值,按照新值来再求一次最值,直到target不变为止,这就满足了Interlocked中所说的,写都在Interlocked之前发生,Interlocked往后就能读到最新的值。

基元内核模式

커널 모드는 운영 체제의 커널 객체 를 사용하여 스레드 동기화 문제를 처리합니다. 먼저 단점에 대해 이야기해 보겠습니다. 속도가 상대적으로 느립니다. 두 가지 이유가 있습니다. 하나는 운영 체제 커널 개체에 의해 구현되고 운영 체제 내에서 조정이 필요하기 때문입니다. 다른 이유는 AppDo를 이해한 후 커널 개체가 모두 관리되지 않는다는 것입니다. 액세스된 개체가 현재 AppDomain에 없으면 값으로 마샬링되거나 참조 로 마샬링됩니다. 관리되지 않는 리소스의 이 부분이 참조로 마샬링되는 것을 관찰한 후에는 성능에 영향을 미칩니다. 위의 두 가지 점을 결합하면 커널 모드의 단점을 얻을 수 있습니다. 그러나 다음과 같은 장점도 있습니다. 1. 스레드가 "회전"하지 않고 리소스를 기다릴 때 차단됩니다. 이렇게 하면 CPU 시간이 절약되고 이 차단에 대한 시간 초과 값을 설정할 수 있습니다. 2. Window 스레드와 CLR 스레드의 동기화가 가능하며, 서로 다른 프로세스의 스레드도 동기화가 가능합니다. (전자는 경험해보지 못했으나 후자의 경우 세마포어에 경계값 자원이 있는 것으로 알려져 있습니다.) 3. 승인된 계정에 대한 접근을 금지하도록 보안 설정을 적용할 수 있습니다(무슨 일인지 모르겠습니다).

커널 모드의 모든 객체에 대한 기본 클래스는 WaitHandle입니다. 커널 모드의 모든 클래스 계층은 다음과 같습니다 >

AutoResetEvent

ManualResetEvent

세마포어

MutexWaitHandle은 관리되지 않는 개체를 참조로 마샬링하는 MarshalByRefObject를 상속합니다. WaitHandle에는 주로 다양한 Wait 메서드가 포함되어 있습니다. Wait 메서드가 호출되면 신호를 받기 전에 차단됩니다. WaitOne은 신호를 기다리고 있고, WaitAny(WaitHandle[] waitHandles)는 모든 waitHandle의 신호를 받고 있으며, WaitAll(WaitHandle[] waitHandles)은 모든 waitHandle의 신호를 기다리고 있습니다. 시간 초과 설정을 허용하는 이러한 메서드 버전이 있습니다. 다른 커널 모드 구성에는 비슷한 Wait 메서드가 있습니다.

EventWaitHandle은 내부적으로 Boolean 값을 유지하며, Wait 메서드는 Boolean 값이 false일 때 스레드를 차단하고 Boolean 값이 true가 될 때까지 스레드를 해제하지 않습니다. 이 부울 값을 조작하는 방법에는 Set() 및 Reset()이 포함됩니다. 전자는 부울 값을 true로 설정하고 후자는 이를 false로 설정합니다. 이는 스위치와 동일합니다. Reset을 호출한 후 스레드는 Wait에 도달할 때까지 일시 중지되고 Set까지 재개되지 않습니다. 비슷한 방식으로 사용되는 두 개의 하위 클래스가 있습니다. 차이점은 AutoResetEvent가 Set을 호출한 후 자동으로 Reset을 호출하므로 스위치가 즉시 닫힌 상태로 돌아가는 반면 ManualResetEvent는 스위치를 닫으려면 Set을 수동으로 호출해야 한다는 것입니다. 일반적으로 AutoResetEvent는 해제될 때마다 하나의 스레드가 통과하도록 허용하는 반면 ManualResetEvent는 Reset을 수동으로 호출하기 전에 여러 스레드가 통과하도록 허용합니다. Semaphore는 내부적으로 정수를 유지합니다. Semaphore 객체를 구성할 때 WaitOne이 호출될 때마다 세마포어가 1씩 증가합니다. 최대값을 설정하면 스레드가 차단됩니다. Release가 호출되면 하나 이상의 세마포가 해제됩니다. 이때 차단된 스레드가 해제됩니다. 이는 생산자와 소비자의 문제와 일치합니다. 생산자가 제품 대기열 에 제품을 계속 추가하면 대기열이 가득 차면 세마포어가 가득 차는 것과 같습니다. 생산자는 차단되며 소비자가 제품을 소비하면 릴리스는 제품 대기열의 공간을 해제합니다. 이때 제품을 저장할 공간이 없는 생산자는 제품 대기열에 제품을 저장하는 작업을 시작할 수 있습니다.

Mutex의 내부 및 규칙은 이전 두 개보다 약간 더 복잡합니다. 우선 이전 스레드와 유사점은 WaitOne을 통해 현재 스레드가 차단되고 스레드 차단이 수행된다는 점입니다. ReleaseMutex를 통해 출시되었습니다. 차이점은 WaitOne을 호출하면 첫 번째 호출 스레드가 통과하도록 허용하고 다른 후속 스레드는 WaitOne을 호출할 때 차단된다는 점입니다. WaitOne을 전달하는 스레드는 WaitOne을 여러 번 호출할 수 있지만 해제하려면 동일한 횟수만큼 ReleaseMutex를 호출해야 합니다. 횟수가 동일하지 않으면 다른 스레드가 차단된 상태로 유지됩니다. 이전 구성과 비교할 때 이 구성에는 스레드 소유권과 재귀라는 두 가지 개념이 있습니다. 이는 추가 캡슐화를 제외하고는 이전 구성에 의존하는 것만으로는 달성할 수 없습니다.

혼합건축

위의 기본 구조는 가장 간단한 구현 방법을 사용합니다. 사용자 모드는 사용자 모드보다 빠르지만 CPU 시간 낭비를 초래합니다. 커널 모드는 이 문제를 해결하지만 각각 장점과 단점이 있습니다. 하이브리드 구조는 두 가지의 장점을 결합하여 내부적으로 특정 전략을 통해 적절한 시점에 사용자 모드를 사용하고, 다른 상황에서는 커널 모드를 사용합니다. 그러나 이러한 판단 계층은 메모리 오버헤드를 가져옵니다. 다중 스레드 동기화에는 완벽한 구조가 없습니다. 각 구조에는 장점과 단점이 있으며, 해당 구조의 존재는 특정 애플리케이션 시나리오와 결합되어 의미가 있습니다. 단지 특정 시나리오에 따라 장단점을 따질 수 있는지 여부에 달려 있습니다.

다양한 Slim 접미사 클래스 System.Threading 네임스페이스 에서 Slim 접미사로 끝나는 여러 클래스(ManualResetEventSlim, SemaphoreSlim, ReaderWriterLockSlim)를 볼 수 있습니다. 마지막을 제외하고 나머지 두 개는 원시 커널 모드에서 동일한 구조를 가지고 있지만 이 세 클래스는 원래 구조를 단순화한 버전이며, 특히 처음 두 클래스는 원래 구조와 동일한 방식으로 사용되지만 시도해 보십시오. 운영 체제의 커널 개체 사용을 피하고 가벼운 효과를 얻으려면 예를 들어 커널 구성인 ManualResetEvent는 SemaphoreSlim에서 사용되지만 이 구성은 지연을 통해 초기화되며 필요한 경우가 아니면 사용되지 않습니다. ReaderWriterLockSlim에 대해서는 나중에 소개하겠습니다.

모니터 및 잠금, 잠금 키워드는 멀티 스레드 동기화를 달성하는 가장 잘 알려진 수단이므로 코드 조각부터 시작하겠습니다

이 방법은 상당히 간단하고 의미가 없습니다. 단지 컴파일러가 이 코드를 무엇으로 컴파일하는지 살펴보기만 하면 됩니다.

IL 코드에 주목하세요. try...finally 문 블록, Monitor.Enter 및 Monotor.Exit 메서드가 추가되었습니다. 그런 다음 코드를 변경하고 다시 컴파일하여 IL

IL 코드

를 확인합니다. 코드 비교 비슷하지만 동일하지는 않습니다. 실제로 lock 문 블록과 동등한 코드는 다음과 같습니다.

lock은 기본적으로 Monitor를 호출하므로 Monitor는 어떻게 The를 전달합니까? 개체가 잠긴 다음 스레드 동기화가 이루어집니다. 관리되는 힙의 모든 개체에는 두 개의 고정 멤버가 있는 것으로 나타났습니다. 하나는 개체 유형의 포인터를 가리키고 다른 하나는 스레드 동기화 블록 인덱스 를 가리킵니다. 이 인덱스는 동기화된 블록 배열 의 요소를 가리킵니다. 모니터는 이 동기화된 블록을 사용하여 스레드를 잠급니다. Jeffrey(C#을 통한 CLR 작성자)에 따르면 동기화 블록에는 소유권 스레드 ID, 대기 스레드 수 및 재귀 수의 세 가지 필드가 있습니다. 그러나 나는 또 다른 기사를 통해 스레드 동기화 블록의 구성원이 단지 소수가 아니라는 것을 알게 되었습니다. 관심 있는 학생들은 "동기화 블록 인덱스 공개"라는 두 기사를 읽어볼 수 있습니다. 모니터가 객체 obj를 잠글 필요가 있으면 obj의 동기화 블록 인덱스가 배열의 인덱스인지 확인합니다. -1인 경우 배열에서 해당 객체와 연결할 사용 가능한 동기화 블록을 찾습니다. 동시에 동기화 블록의 소유권 스레드 ID는 현재 스레드의 ID를 기록합니다. 스레드가 모니터를 다시 호출하면 동기화 블록의 소유권 ID가 현재 스레드 ID와 일치하는지 확인합니다. 재귀에서 횟수에 1을 더합니다. 스레드를 일치시킬 수 없으면 스레드를 준비 대기열(이 대기열은 실제로 동기화 블록에 있음)에 넣고 이 동기화 블록이 숫자를 확인합니다. 재귀가 완료된 후 소유권 스레드 ID가 지워지는지 확인하기 위해 Exit를 호출할 때 재귀의 수를 확인합니다. 대기 중인 스레드가 있는지 여부는 대기 중인 스레드에서 제거되고 해제됩니다. 그렇지 않으면 동기화 블록과의 연결이 해제되고 다음 동기화 블록이 사용되기를 기다립니다. 잠긴 개체.

모니터에는 Wait와 Pulse 두 가지 방법이 있습니다. 전자는 잠금을 획득한 스레드가 잠시 잠금을 해제하도록 할 수 있으며, 현재 스레드는 차단되어 대기 대기열에 배치됩니다. 다른 스레드가 Pulse 메서드를 호출할 때까지 해당 스레드는 대기 큐에서 준비 큐로 들어가며, 다음에 잠금이 해제되면 상황에 따라 다시 잠금을 획득할 수 있는 기회가 있습니다. 대기 대기열에 있습니다.

ReaderWriterLock은 전통적인 잠금 키워드입니다(모니터의 Enter 및 Exit와 동일). 공유 리소스에 대한 잠금은 잠긴 리소스가 잠기면 다른 리소스가 전혀 액세스할 수 없습니다.

而ReaderWriterLock对互斥资源的加的锁分读锁与写锁,类似于数据库中提到的共享锁和排他锁。大致情况是加了读锁的资源允许多个线程对其访问,而加了写锁的资源只有一个线程可以对其访问。两种加了不同缩的线程都不能同时访问资源,而严格来说,加了读锁的线程只要在同一个队列中的都能访问资源,而不同队列的则不能访问;加了写锁的资源只能在一个队列中,而写锁队列中只有一个线程能访问资源。区分读锁的线程是否在于统一个队列中的判断标准是,本次加读锁的线程与上次加读锁的线程这个时间段中,有否别的线程加了写锁,没没别的线程加写锁,则这两个线程都在同一个读锁队列中。

ReaderWriterLockSlim和ReaderWriterLock类似,是后者的升级版,出现在.NET Framework3.5,据说是优化了递归和简化了操作。在此递归策略我尚未深究过。目前大概列举一下它们通常用的方法

ReaderWriterLock常用的方法

Acqurie或Release ReaderLock或WriteLock 的排列组合

UpGradeToWriteLock/DownGradeFromWriteLock 用于在读锁中升级到写锁。当然在这个升级的过程中也涉及到线程从读锁队列切换到写锁队列中,因此需要等待。

ReleaseLock/RestoreLock 释放所有锁和恢复锁状态

ReaderWriterLock实现IDispose接口,其方法则是以下模式

TryEnter/Enter/Exit ReadLock/WriteLock/UpGradeableReadLock

CoutdownEvent比较少用的混合构造,这个跟Semaphore相反,体现在Semaphore是在内部计数(也就是信号量)达到最大值的时候让线程阻塞,而CountdownEvent是在内部计数达到0的时候才让线程阻塞。其方法有

AddCount //计数递增;
Signal //计数递减;
Reset //计数重设为指定或初始;
Wait //当且仅当计数为0才不阻塞,否则就阻塞。

Barrier也是一个比较少用的混合构造,用于处理多线程在分步骤的操作中协作问题。它内部维护着一个计数,该计数代表这次协作的参与者数量,当不同的线程调用SignalAndWait的时候会给这个计数加1并且把调用的线程阻塞,直到计数达到最大值的时候,才会释放所有被阻塞的线程。假设还是不明白的话就看一下MSND上面的示例代码

这里给Barrier初始化的参与者数量是3,同时每完成一个步骤的时候会调用委托,该方法是输出count的值步骤索引。参与者数量后来增加了两个又减少了一个。每个参与者的操作都是相同,给count进行原子自增,自增完则调用SgnalAndWait告知Barrier当前步骤已完成并等待下一个步骤的开始。但是第三次由于回调方法里抛出了一个异常,每个参与者在调用SignalAndWait的时候都会抛出一个异常。通过Parallel开始了一个并行操作。假设并行开的作业数跟Barrier参与者数量不一样就会导致在SignalAndWait会有非预期的情况出现。

接下来说两个Attribute,这个估计不算是同步构造,但是也能在线程同步中发挥作用

MethodImplAttribute这个Attribute适用于方法的,当给定的参数是MethodImplOptions.Synchronized,它会对整个方法的方法体进行加锁,凡是调用这个方法的线程在没有获得锁的时候就会被阻塞,直到拥有锁的线程释放了才将其唤醒。对静态方法而言它就相当于把该类的类型对象给锁了,即lock(typeof(ClassType));对于实例方法他就相当于把该对象的实例给锁了,即lock(this)。最开始对它内部调用了lock这个结论存在猜疑,于是用IL编译了一下,发现方法体的代码没啥异样,查看了一些源码也好无头绪,后来发现它的IL方法头跟普通的方法有区别,多了一个synchronized

于是网上找各种资料,最后发现"junchu25"的博客[1][2]里提到用WinDbg来查看JIT生成的代码。

调用Attribute的

调用lock的

对于用这个Attribute实现的线程同步连Jeffrey都不推荐使用。

System.Runtime.Remoting.Contexts.SynchronizationAttribute는 클래스 정의에 이 특성을 추가하고 ContextBoundOject의 클래스를 상속합니다. 이는 클래스의 모든 메서드에 동일한 잠금을 추가합니다. . 스레드가 이 클래스의 메서드를 호출할 때 잠금이 획득되지 않으면 스레드가 차단됩니다. 본질적으로 잠금을 호출한다는 말이 있는데, 이 문을 확인하는 것은 훨씬 더 어렵습니다. AppDomain 및 스레드 컨텍스트도 포함하는 국내 리소스가 거의 없습니다. AppDomain은 별도의 문서에서 소개해야 합니다. 그런데 여기서 조금 이야기하고 싶은데, 예전에는 메모리에 스레드 스택과 힙 메모리가 있다고 생각했는데, 이것도 아주 기본적인 구분일 뿐이고 힙 메모리도 여러 개의 AppDomain으로 나누어져 있습니다. 각 AppDomain에 있는 하나 이상의 컨텍스트. 각 개체는 AppDomain 내의 컨텍스트에 속합니다. AppDomain의 개체는 직접 액세스할 수 없습니다. 값으로 마샬링하거나(호출하는 AppDomain에 개체를 전체 복사하는 것과 동일) 참조로 마샬링해야 합니다. 참조에 의한 마샬링의 경우 클래스는 MarshalByRefObject를 상속해야 합니다. 이 클래스를 상속받은 객체를 호출하면 클래스 자체를 호출하지 않고 프록시를 통해 호출합니다. 그런 다음 값 연산을 통한 컨텍스트 간 마샬링도 필요합니다. 일반적으로 생성된 개체는 프로세스의 기본 AppDomain 아래 기본 컨텍스트에 있으며,SynchronizationAttribute 특성을 사용하는 클래스의 인스턴스는 다른 컨텍스트에 속합니다. ContextBoundObject 기본 클래스를 상속하는 클래스도 프록시를 사용하여 개체에 액세스합니다. 참조 마샬링에 의한 객체는 객체 자체에 액세스하지 않습니다. 컨텍스트를 통해 개체에 액세스할지 여부는 RemotingServices.IsObjectOutOfContext(obj) 메서드를 통해 판단할 수 있습니다. SynchronizedServerContextSink는 mscorlib의 내부 클래스입니다. 스레드가 컨텍스트 간 개체를 호출하면 호출은 mscorlib의 내부 클래스이기도 한 동기화된 서버 컨텍스트 싱크에 의해 작업 항목 개체로 캡슐화됩니다. 동기화 속성은 여러 작업 항목 실행 요청이 있는지 여부에 따라 현재 실행 요청을 결정합니다. 처리된 WorkItem은 즉시 실행됩니까, 아니면 순서대로 실행되기 위해 선입선출 작업 항목 대기열에 배치됩니까? 이 대기열은 대기열 멤버가 대기열에 들어오고 나갈 때 또는 속성이 WorkItem을 즉시 실행하려면 잠금을 획득해야 하며 잠긴 개체도 이 WorkItem의 대기열입니다. 여기에는 여러 클래스의 상호 작용이 포함됩니다. 아직 완전히 이해하지 못했습니다. 위 과정에서 오류가 있을 수 있습니다. 명확한 분석 후 추가하겠습니다. 그러나 이 속성을 통해 달성된 스레드 동기화는 주로 성능 손실과 잠금 범위가 상대적으로 크기 때문에 강한 직관에 따라 권장되지 않습니다.

위 내용은 C# 멀티스레딩의 스레드 동기화에 대한 자세한 설명(그림 및 텍스트)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.