2009-02-02 3 views
42

이것은 C#의 세부적인 질문입니다.get/set을 통한 C# 스레드 안전

내가 객체와 클래스를 가지고 가정, 그 객체는 잠금 장치에 의해 보호됩니다 :

Object mLock = new Object(); 
MyObject property; 
public MyObject MyProperty { 
    get { 
     return property; 
    } 
    set { 
     property = value; 
    } 
} 

내가 폴링 스레드가 해당 속성을 쿼리 할 수 ​​있어야합니다. 또한 스레드가 가끔씩 해당 객체의 속성을 업데이트하고 사용자가 해당 속성을 업데이트 할 수 있으며 사용자가 해당 속성을 볼 수 있기를 원할 때도 있습니다.

다음 코드가 데이터를 제대로 잠글 것입니까? 나는 업데이트를 할 때

MyProperty.Field1 = 2; 

이든, 필드가 잠 깁니다을 호출 할 경우

'제대로'으로
Object mLock = new Object(); 
MyObject property; 
public MyObject MyProperty { 
    get { 
     lock (mLock){ 
      return property; 
     } 
    } 
    set { 
     lock (mLock){ 
       property = value; 
     } 
    } 
} 

, 내 말은 무엇입니까? 'get'함수의 범위 내에서 equals 연산자로 수행되는 설정입니까, 아니면 'get'함수 (따라서 잠금)가 먼저 완료되고 설정이 끝난 다음 'set'이 호출되어 바이 패스됩니다. 자물쇠?

편집 : 이것은 분명히 트릭을 수행하지 않으므로, 무엇을 할 예정입니까? 내가 좋아하는 뭔가를 어떻게해야합니까 : 더 많거나 적은 그냥 내가 가지고있는 것 인 경우에 두 개의 스레드가 동시에 '수'를 호출 즉, 난 단지 사본에 액세스 할 수 있는지 확인합니다

Object mLock = new Object(); 
MyObject property; 
public MyObject MyProperty { 
    get { 
     MyObject tmp = null; 
     lock (mLock){ 
      tmp = property.Clone(); 
     } 
     return tmp; 
    } 
    set { 
     lock (mLock){ 
       property = value; 
     } 
    } 
} 

을, 그들은 각각 Field1과 같은 값으로 시작합니다 (맞습니까?). 거기에 읽을 수있는 방법과 속성을 읽을 때 잠금을 쓰기 이해가 되니? 아니면 데이터 그 자체가 아닌 함수 섹션을 잠그는 것에 전념해야합니까?

이 예제가 의미가 있습니다. MyObject는 상태를 비동기 적으로 반환하는 장치 드라이버입니다. 직렬 포트를 통해 명령을 보내면 장치가 자신의 달콤한 시간에 해당 명령에 응답합니다. 지금 당장은 상태에 대해 폴링하는 스레드가 있습니다 ("아직 명령 있습니까?"), 직렬 포트에서 응답을 기다리는 스레드 ("상태 문자열 2, 모든 것이 모두 양호 함") 그리고 다른 명령 ("사용자가이 일을하기를 원합니다.")을 취하는 UI 스레드와 드라이버의 응답을 게시합니다 ("방금 작업을 완료했습니다. 이제 UI를 업데이트합니다"). 그래서 개체의 필드보다는 개체 자체를 잠그고 싶습니다. 그것은 엄청난 수의 잠금 장치, a, b가 될 것입니다.이 클래스의 모든 장치가 동일한 동작을하는 것은 아니며 일반 동작이므로 잠금을 개별화하면 개별 대화 상자를 많이 코딩해야합니다.

답변

34

아니요, 코드는 MyProperty에서 반환 된 개체의 멤버에 대한 액세스를 잠그지 않습니다. MyProperty 자체 만 잠급니다.

귀하의 사용 예는 두 가지 작업이 거의 비슷 하나에 압연 정말 :

간단히 말해서
// object is locked and then immediately released in the MyProperty getter 
MyObject o = MyProperty; 

// this assignment isn't covered by a lock 
o.Field1 = 2; 

// the MyProperty setter is never even called in this example 

- 두 스레드가 동시에 MyProperty에 액세스하는 경우가 반환 될 때까지의 게터 간단히 두 번째 스레드를 차단합니다 객체를 첫 번째 스레드 인 으로 옮기고을 호출하면 객체를 두 번째 스레드로 반환합니다. 그러면 두 스레드 모두 개체에 대한 완전하고 잠금 해제 된 액세스 권한을 갖게됩니다. 문제의 자세한 내용에 응답

편집

나는 아직도 당신이 달성하려고하는 것을 100 % 확신하지만, 당신은 다음 개체에 원자 액세스하려는 경우 개체 자체에 대한 호출 코드 잠금을 가질 수 없습니까? 이상하지만, 객체에 대한 모든 액세스가 lock (MyProperty) 섹션에 포함되어 있습니다로 너무 오래 다음이 접근 방식은 스레드 안전하지 않습니다

// quick and dirty example 
// there's almost certainly a better/cleaner way to do this 
lock (MyProperty) 
{ 
    // other threads can't lock the object while you're in here 
    MyProperty.Field1 = 2; 
    // do more stuff if you like, the object is all yours 
} 
// now the object is up-for-grabs again 

.

+0

동의. 싱글 톤 질문에 대한 응답으로 묻는 샘플 솔루션을 제공했습니다. http://stackoverflow.com/questions/7095/is-the-c-static-constructor-thread-safe/7105#7105 – Zooba

+0

예, 당신이 기술 한 것처럼 객체를 잠그는 것이 좋습니다. 감사. – mmr

+0

좋은 점은, 그가'p'라는 이름의'MyProperty'를 가지고 있고 동시에 두 개의 스레드를 호출했다면'p = new MyObject (12)'와'p = new MyObject (5)'입니다. 그러면 * this * . 그러나'Field1' 멤버는 잠기지 않습니다. 나는 이것이 당신이 말하고있는 것임을 확신합니다. 단지 그것을 이해하려고 노력하고 있습니다. – Snoopy

1

게시 한 코드 예제에서 get은 수행되지 않습니다. 더 복잡한 예에서

:

MyProperty.Field1 = MyProperty.doSomething() + 2; 

물론 당신이 한 가정 A : 다음 doSomething()에서

lock (mLock) 
{ 
    // stuff... 
} 

잠금 전화의 모든 하지를 통해 동기화를 보장하기에 충분 것 전체 개체. doSomething() 함수가 반환 되 자마자 잠금이 손실 된 다음 추가가 수행 된 후 할당이 다시 발생하여 다시 잠 깁니다.

또는, 당신은이 amutomatically하지 잠금 척 등을 한 줄에 하나의 조작으로 "기계 코드"와 같은이를 다시 작성할 수있는 다른 방법을 쓰고, 그것을 분명하게하기 :

lock (mLock) 
{ 
    val = doSomething() 
} 
val = val + 2 
lock (mLock) 
{ 
    MyProperty.Field1 = val 
} 
+0

hunh. 어쩌면 속성을 잘못 이해하고 있지만 디버거가 'get'함수로 들어가는 것처럼 보입니다. 특히 간단한 할당을 위해 여기에 중단 점을 넣을 때 특히 그렇습니다. – mmr

+0

나는 그것이 get을 호출하지 않는다는 것을 100 % 확신하지는 못 하겠지만, 왜 그런지는 알 수 없다. 그것이 get을 호출한다면, 내가 말했던 것과 똑같은, doSomething()을 get 함수로 대체하십시오. – SoapBox

+0

매번 get을 호출합니다. 그렇지 않으면 참조는 어디서 왔습니까? –

1

멀티 스레딩의 장점은 어떤 일이 일어날 지 알지 못한다는 것입니다. 한 스레드에서 뭔가를 설정하면 먼저 일어날 수 있고, get 후에 발생할 수 있습니다.

게시 한 코드는 읽고 쓰는 동안 회원을 잠급니다. 값이 업데이트되는 경우를 처리하려면 events과 같은 다른 형식의 동기화를 조사해야합니다. (자동/수동 버전을 확인하십시오). 그런 다음 "폴링"스레드에게 값이 변경되었고 다시 읽을 준비가되었음을 알릴 수 있습니다.

2

예제의 잠금 범위가 잘못되었습니다 - 컨테이너가 아닌 'MyObject'클래스의 속성 범위에 있어야합니다.

MyObject my 객체 클래스가 하나의 스레드가 쓰고 자하는 데이터를 단순히 포함하고 다른 스레드 (UI 스레드)를 읽는 경우에는 세터가 필요하지 않고 한 번만 생성 될 수 있습니다.

속성 수준의 잠금 배치가 잠금 단위의 쓰기 수준인지 여부도 고려하십시오. 트랜잭션의 상태 (예 : 전체 주문 및 총중량)를 나타 내기 위해 둘 이상의 속성을 쓸 수있는 경우 MyObject 수준에서 잠금을 설정하는 것이 좋습니다 (예 : lock (myObject.SyncRoot)). .)

0

편집 한 버전에서는 여전히 MyObject를 업데이트하기위한 스레드 안전 방안을 제공하지 않습니다. 객체의 속성을 변경하면 동기화 된/잠긴 블록 내에서 수행해야합니다.

이 설정을 처리하기 위해 개별 설정기를 작성할 수 있지만 많은 수의 필드로 인해 어려울 수 있다고 지적했습니다.실제로 (그리고 이것을 평가하기에 충분한 정보를 아직 제공하지 못했다면), 대안은 반사를 사용하는 세터를 작성하는 것입니다. 이렇게하면 필드 이름을 나타내는 문자열을 전달할 수 있으며 필드 이름을 동적으로 검색하고 값을 업데이트 할 수 있습니다. 이렇게하면 여러 필드에서 작업 할 수있는 단일 설정자가 가능합니다. 이것은 쉽지는 않으나 효율적이지만 많은 수의 클래스와 필드를 처리 할 수 ​​있습니다.

12

접근 방식이 효과적이라면 동시 프로그래밍이 매우 쉽습니다. 나쁜 사람이있다,

objectRef.MyProperty += 1; 

읽기 - 수정 - 쓰기 경주는 매우 분명하다 :하지만 타이타닉 예를 들어, 클래스의 클라이언트가이 일이라고 싱크 빙산하지 않습니다. 속성을 스레드로부터 안전하게 만들 수있는 방법은 없습니다. 두통에 대처해야하는 것은 고객입니다. 이런 종류의 책임을 프로그래머에게 위임하지 않으면 안되는 프로그래머는 동시 프로그래밍의 아킬레스 건을들 수 있습니다.

+0

이것이 쉬운 일인가? 이유가 무엇인지는 확실 합니다만, 코드가 왜 이렇게 작동하지 않는지를 알기에 충분하지는 않습니다. – mmr

+0

이것을 이해하는 것이 중요합니다. 그렇지 않은 경우 동시 프로그래밍을 시도하지 마십시오. 구글의 "원자력 업데이트". –

+0

나는 원 자성을 이해하지만, C#에서는 일들이 원자 적이라는 곳에 어렴풋이 드러납니다. 기본 유형 할당뿐입니다. 연동과 같은 것으로 원 자성을 지정하면 유용 할 수 있습니다. – mmr

4

다른 사람들이 지적했듯이 getter에서 객체를 반환하면 객체에 액세스하는 사람과 객체를 제어 할 수 없게됩니다. 당신이하고 싶은 것을하기 위해서, 당신은 객체 자체 안에 자물쇠를 넣어야 할 것입니다.

아마도 전체 그림을 이해하지 못 하겠지만 설명에 따르면 아마도 개별 필드마다 잠금 장치가 있어야하는 것처럼 들리지는 않습니다. getters와 setter를 통해 일련의 필드를 읽고 쓰는 것만으로도이 필드에 대해 단일 잠금을 해제 할 수 있습니다. 이 방법으로 쓰레드의 동작을 불필요하게 직렬화 할 가능성이 분명합니다. 하지만 다시 한 번 설명에 따르면, 공격적으로 객체에 액세스하는 것처럼 들리지는 않습니다.

또한 스레드를 사용하여 장치 상태를 폴링하는 대신 이벤트를 사용하는 것이 좋습니다. 폴링 메커니즘을 사용하면 스레드가 장치를 쿼리 할 때마다 잠금이 해제됩니다. 이벤트 메커니즘을 사용하면 상태가 변경되면 개체가 모든 수신기를 알립니다. 그 시점에서 폴링중인 스레드 (더 이상 폴링하지 않음)가 깨어나 새로운 상태가됩니다. 이것은 훨씬 더 효율적입니다. 예를 들어

...

public class Status 
{ 
    private int _code; 
    private DateTime _lastUpdate; 
    private object _sync = new object(); // single lock for both fields 

    public int Code 
    { 
     get { lock (_sync) { return _code; } } 
     set 
     { 
      lock (_sync) { 
       _code = value; 
      } 

      // Notify listeners 
      EventHandler handler = Changed; 
      if (handler != null) { 
       handler(this, null); 
      } 
     } 
    } 

    public DateTime LastUpdate 
    { 
     get { lock (_sync) { return _lastUpdate; } } 
     set { lock (_sync) { _lastUpdate = value; } } 
    } 

    public event EventHandler Changed; 
} 

귀하의 '폴링'스레드는 다음과 같이 보일 것입니다.

Status status = new Status(); 
ManualResetEvent changedEvent = new ManualResetEvent(false); 
Thread thread = new Thread(
    delegate() { 
     status.Changed += delegate { changedEvent.Set(); }; 
     while (true) { 
      changedEvent.WaitOne(Timeout.Infinite); 
      int code = status.Code; 
      DateTime lastUpdate = status.LastUpdate; 
      changedEvent.Reset(); 
     } 
    } 
); 
thread.Start(); 
+0

흥미 롭습니다. 이것 좀 봐야 겠어. 저의 첫 번째 인상은 작동하지 않을 것입니다. 왜냐하면 내가 사용하고있는 장치가 묻지 않는 한 상태 메시지를 내지 않기 때문입니다. 따라서 '준비'모드 인 경우 요청하지 않으면 표시되지 않습니다. – mmr

+0

그래, 상태를보고 할 장치를 자극해야한다면, 폴링이 당신이 할 수있는 유일한 방법 일 것입니다. 우연히, 장치가 주기적으로 상태를보고하게하는 콜백 메커니즘이 있습니까? 비효율적이기 때문에 투표가 싫지만 때로는 선택의 여지가 없습니다. –

+0

불행하게도 이것은 진정한 시리얼 장치입니다. 폴링에만 응답합니다. 이것은 USB가 아니며 9 핀입니다. 올드 스쿨 레트로 줄곧. – mmr

-1

합니까 C#을 잠금 후 다른 언어와 같은 잠금 문제를 겪지 :

나는이 상황에서 흥미로운 일이 될 수있는 C#에서 불변의 모델 클래스에 관한 기사를 작성했습니다?

E.G.

var someObj = -1; 

// Thread 1 

if (someObj = -1) 
    lock(someObj) 
     someObj = 42; 

// Thread 2 

if (someObj = -1) 
    lock(someObj) 
     someObj = 24; 

이렇게하면 두 스레드가 결국 잠금을 해제하고 값을 변경하는 문제가 발생할 수 있습니다. 이것은 이상한 버그를 초래할 수 있습니다. 그러나 필요하지 않으면 개체를 불필요하게 잠그고 싶지는 않습니다. 이 경우 이중 확인 잠금을 고려해야합니다.

// Threads 1 & 2 

if (someObj = -1) 
    lock(someObj) 
     if(someObj = -1) 
      someObj = {newValue}; 

염두에 두어야 할 것이 있습니다.