[Thread - Unsafe의 예제]
멀티스레드는 필드의 내용을 동시에 액세스 하여 잘못된 결과를 만들거나 출력할 수 있는 이를 Thread unsafe라고 한다.
아래 예제에는 여러 개의 스레드가 Thread - Safe하지 않은 메서드를 호출하는 예를 보여준다.
10개의 스레드가 counter라는 필드를 동시에 쓰거나 읽는 샘플로서 한 스레드가 counter 변수를 변경하고 읽기 전에 다른 스레드가 다시 counter 변수로 변경할 수 있기 때문에 불확실한 결과를 출력하게 된다.
# 예제
using System;
using System.Threading;
namespace MultiThreadApp {
class MyClass {
private int counter = 1000;
public void Run() {
// 10개의 스레드가 동일 메서드 실행
for (int i = 0; i < 10; i++) {
new Thread (UnsafeCalc).Start();
}
}
// Thread - Safe 하지 않은 메서드
private void UnsafeCalc() {
// 객체 필드를 모든 스레드가 자유롭게 변경
counter++;
// 가정 : 다른 복잡한 일을 한다.
for (int i = 0; i <counter; i++) {
for (int j = 0; j <counter; j++) {
// 필드 값 읽기
Console.WriteLine(counter);
}
}
}
}
}
[lock 키워드]
특정 블록의 코드(Critical Section이라 부른다.)를 한 번에 하나의 스레드만 실행 할 수 있도록 해준다.
lock()의 파라미터에는 임의의 객체를 사용할 수 있는데, 주로 object 타입의 private 필드를 지정한다.
즉, private object obj = new object()와 같이 private 필드를 생성한 후, lock(obj)와 같이 지정한다.
특히, 클래스 객체를 의미하는 this를 사용하여 lock(this)와 같이 잘못 사용할 수 있는데, 이는 의도치 않게 데드락을 발생시키거나 Lock Granularity를 떨어뜨리는 부작용을 야기할 수 있다.
즉, 불필요하게 객체 전체 범위에서 lock을 걸어 다수의 메서드가 공유 리소스를 접근하는데 Lock Granularity를 떨어뜨리는 효과가 있으며 (주 : 이 경우 필드나 속성들이 잠긴다는 의미는 아니며, 이들 필드나 속성을 계속 읽고 쓸 수 있음),
또한 만약 외부에서 해당 클래스 객체를 lock 하고 그 객체 안의 메서드를 호출한 경우 그리고 그 메서드 안에 다시 lock(this)를 하는 경우,
이미 외부에서 잡혀 있는 lock이 풀리기를 메서드 내에서 계속 기다릴 것이므로 데드락이 발생할 수 있다.
그리고 Critical Section 코드 블록은 가능한 한 범위를 작게 하는 것이 좋은데, 이는 필요한 부분만 Locking 한다는 원칙에 따른 것이다.
# 예제
using System;
using System.Threading;
namespace MultiThrdApp
{
class MyClass
{
private int counter = 1000;
// lock문에 사용될 객체
private object lockObject = new object();
public void Run()
{
// 1o개의 스레드가 동일 메서드 실행
for (int i = 0; i < 10; i++)
{
new Thread(SafeCalc).Start();
}
}
// Thread-Safe 메서드
private void SafeCalc()
{
// 한 번에 한 스레드만 lock 블럭 실행
lock (lockObject)
{
// 필드 값 변경
counter++;
// 가정 : 다른 복잡한 일을 한다.
for (int i = 0; i < counter; i++)
{
for (int j = 0; j < counter; j++)
{
// 필드 값 읽기
Console.WriteLine(counter);
}
}
}
}
}
}
// 소스코드 출력
1001
1002
1003
1004
...
** Lock("") 안에 들어가는 객체 (this나 lockob1 등)는 열쇠라고 가정한다. "" 안에 this를 넣게 되면 집 현관 키를 사용하게 되는 것이다. 즉, 내가 집에 들어가면 다른 사람은 집에 들어오지 못하게 된다. (반대로 나오지 못한다고도 볼 수 있다.) 특정 object(lockob1)은 집 안에 방이라고 가정하면, 내가 집 안에 특정 방 열쇠를 갖게 되고 그 방만 점유하게 된다. 그러면 다른 사람(어떤 이벤트나 액션, 스레드 등)은 집 안의 또 다른 방에서 접근이 가능해진다.
Q. int형 변수가 있을 경우 한 스레드는 변수 값 변경, 한 스레드는 변수 값을 읽기만 하는 역할일 경우 lock을 걸어야 하나?
A. 해당 변수를 2개의 스레드에서 액세스하기 때문에 lock을 사용해야 한다.
Q. 스레드가 아닌 곳에서 lock을 사용한다면 lock의 기능을 하지 않나?
A. 모든 코드에 스레드가 실행한다. 스레드가 실행하지 않는 특정한 곳은 없지만 만약 lock을 여러 스레드가 동시에 액세스 하지 않는 한 스레드만 존재하는 곳에 사용하게 된다면, 일시적으로 lock을 걸었다가 풀게 될 것이다.
'공부중 > 작업하기' 카테고리의 다른 글
| [C#] Byte Array - String 데이터 전송 시 주의사항 (0) | 2022.08.03 |
|---|---|
| [C#] SubString - 문자열 뒤부터 자르기 (0) | 2022.08.03 |
| [Linux] VMWare - CentOS 8 설치 (0) | 2022.07.28 |
| [Windows] 서비스 생성/삭제 (0) | 2022.07.27 |
| SVN 오류 (feat. 불필요한 파일 빼기) (0) | 2022.07.27 |