본문 바로가기

C#

[PostSharp] ReaderWriterSynchronized

PostSharp의 ReaderWriterSynchronized기능을 이용하면 원본 코드의 수정 없이 이미 작성한 클래스를 Thread-Safe 하도록 만들 수 있습니다.

명심해야 할 것은, PostSharp에서 제공하는 쓰레딩 모델들은 개발 편의성이 중시되어, 직접 제어되는 락보다 좋은 성능을 내지는 못합니다.


간단하게 카운터 클래스를 만들고 여러 스레드에서 동시에 반복적으로 카운트를 올리는 작업을 수행해 보도록 하겠습니다.

 

당연한 이야기지만, 제대로된 결과가 나오지 않습니다.

이제 PostSharp을 사용하여 위의 클래스를 Thread-Safe 하게 만들어보도록 하겠습니다.


먼저 우리가 사용할 ReaderWriterSynchronized 모델은 PostSharp.Patterns.Threading 네임스페이스에 정의되어 있습니다. 해당 네임스페이스를 using 하여줍니다. (없다면 NuGet을 통해 추가합니다.)

 

쓰레딩 모델을 적용할 클래스에 ReaderWriterSynchronizedAttribute를 추가합니다.
 

마지막으로 메소드별로 역할에 맞게 ReaderAttribute, WriterAttribute를 추가합니다.
Writer는 공유 자원에 쓰기 작업을 하는 메소드로, 여기서는 Increase가 Writer입니다.
Reader는 공유 자원에 읽기 작업을 하는 메소드로, 여기서는 Read가 Reader입니다.


이제 코드를 다시 실행해보면 정상적으로 결과가 출력되는것을 볼 수 있습니다.


비정적 프로퍼티에 접근하는 모든 공개 메소드는 Reader 또는 Writer가 적용되어 있어야 합니다.

만약 아무것도 설정되지 않은 메소드가 인스턴스 프로퍼티에 접근하면 런타임 에러가 발생합니다.



이제 메소드들 외에 클래스의 프로퍼티들에 대해서는 어떻게 사용해야 하는지 알아보도록 하겠습니다.

public으로 설정된 프로퍼티들은 클래스 외부로 공개되는 인터페이스들입니다. 따라서 Thread-Safe한 클래스의 모든 공개되는 프로퍼티들조차도 쓰레드에서 안전해야 할 필요가 있습니다.
이런 이유로, PostSharp에서는 ReaderWriterSynchronized속성을 가진 클래스 아래의 모든 public 프로퍼티들에 대해서 자동으로 getter에 ReaderAttribute를, setter에 WriterAttribute를 설정하여줍니다.

* 주의해야 할 점은, 자동으로 getter에 ReaderLock이, setter에 WriterLock이 적용된다고 해서 서 프로퍼티를 다루는 모든 코드가 Thread-Safe하게 동작하는것은 아닙니다.
예를 들어 아래의 코드는 Reader/Writer가 getter/setter에 적용되어 있는 상태라고 하더라도 쓰레드에 안전하지 않습니다.


아시다시피 num += 1 은 단일연산이 아닙니다. 위의 코드는 아래와 같이 풀어 쓸 수 있습니다.

'num의 getter로 값을 얻어온 다음 -> 1을 더하고 -> setter를 이용해 값을 저장합니다.'

각각 getter, setter를 다녀오는 동작은 Thread-Safe이지만 전체 동작은 전혀 Thread-Safe하지 않습니다.


위의 예제에서 num ++; 코드가 정상적으로 동작하는 이유는, Increase 메소드 자체가 WriterAttribute가 적용되어 묶여있기 때문입니다.

반대로 public이 아닌 프로퍼티들에서는 PostSharp이 자동으로 ReaderAttibute, WriterAttribute를 삽입하지 않고, 개발자로 하여금 필요한 곳에만 사용하여 선택할 수 있도록 합니다. 

클래스가 Thread-Safe하다고 해서, 내부적으로 사용되는 프로퍼티까지 항상 Thread-Safe할 필요는 없습니다,
예를들어, 우리가 위에 만든 Foo 클래스의 private int num 의 getter/setter에는 아무런 Reader/Writer설정도 되어 있지 않았는데 어떻게 Thread-Safe하게 동작할까요?
그것은 이미 메소드 자체게 Writer/Reader 설정이 되어 있어, 접근하는 메소드가 Thread-Safe인지 여부에 관계 없이 메소드의 동작 자체가 동기화되었기 때문입니다.
따라서 모든 프로퍼티들을 무조건적으로 Thread-Safe하게 만드는것은 성능 저하를 부를 수 있습니다.


마지막으로 UpgradeableReader에 대해서 알아보도록 하겠습니다.

아시다시피 WriterLock은 무거운 작업입니다. 큰 계산을 한 후에 그 값을 공유 자원에 저장하는 메소드가 있다고 할 때 이런 메소드에 WriterAttribute를 사용하는것은 굉장히 비효율적인 행동입니다.


이러한 문제를 해결하기 위한것이 바로 UpgradeableReader입니다.
UpgradeableReader가 적용된 메소드는 처음에는 Reader로 시작하다가, Writer를 사용해야 하는 부분을 만나면 그순간부터 Writer로 업그레이드됩니다.

주의해야 할 것은 위에도 설명되어있지만, private 프로퍼티는 기본적으로 setter가 Writer가 아닙니다. 따라서 아래 코드를 실행하면 익셉션이 발생합니다. num에 대한 setter에 WriterAttribute가 적용되지 않아, num에 쓰기 작업을 호출할 때 Increase 메소드가 Writer로 업그레이드 되지 않았기 때문입니다.


'C#' 카테고리의 다른 글

[C#] string과 String의 차이  (0) 2015.11.11
[C#] Using static 사용하기  (0) 2015.10.21
[C#] Thread.SpinWait와 SpinWait 구조체의 차이  (0) 2015.10.20
[C#] Mixin 흉내내기  (0) 2015.10.20
[C#] Thread.Sleep의 await(Task) 버전  (0) 2015.10.19