멀티스레드 환경에서는 여러 스레드가 동시에 하나의 Stack 컬렉션에 접근할 수 있으며, 이 경우 데이터 손상이나 예기치 않은 오류가 발생할 수 있습니다. 이러한 문제를 방지하려면 lock 문과 SyncRoot 속성을 함께 사용하여 동기화된 액세스를 구현해야 합니다.
스택 동기화의 기본 원리
System.Collections 네임스페이스의 Stack 클래스는 기본적으로 스레드로부터 안전(thread-safe)하지 않습니다. 특히 foreach문을 사용해 컬렉션을 열거하는 도중에 다른 스레드가 요소를 추가하거나 삭제하면 예외가 발생할 수 있습니다. Stack 클래스가 제공하는 SyncRoot 속성은 컬렉션에 대한 액세스를 동기화하는 데 사용되는 객체를 반환하며, 이 객체를 lock 블록으로 감싸면 해당 영역에서 한 번에 하나의 스레드만 컬렉션에 접근하도록 보장할 수 있습니다.
예제 1: 정수 요소를 가진 스택 동기화
using System;
using System.Collections;
public class Demo {
public static void Main() {
Stack stack = new Stack();
stack.Push(100);
stack.Push(200);
stack.Push(300);
stack.Push(400);
stack.Push(500);
Console.WriteLine("Stack...");
foreach(Object ob in stack) {
Console.WriteLine(ob);
}
Console.WriteLine("Count of elements = "+stack.Count);
Console.WriteLine("Synchronize access...");
lock(stack.SyncRoot) {
foreach(Object ob in stack) {
Console.WriteLine(ob);
}
}
}
}출력 결과
위 코드를 실행하면 다음과 같은 결과가 출력됩니다.
Stack... 500 400 300 200 100 Count of elements = 5 Synchronize access... 500 400 300 200 100
스택은 LIFO(Last-In-First-Out, 후입선출) 구조이므로 마지막에 Push한 500부터 순서대로 출력됩니다. lock(stack.SyncRoot) 블록 내부의 열거 작업은 다른 스레드의 간섭 없이 안전하게 수행됩니다.
예제 2: 문자열 요소를 가진 스택 동기화
이번에는 문자열 데이터를 저장한 스택에 동일한 방식으로 동기화된 액세스를 적용해 보겠습니다.
using System;
using System.Collections;
public class Demo {
public static void Main() {
Stack stack = new Stack();
stack.Push("Jacob");
stack.Push("Tim");
stack.Push("Philips");
stack.Push("Tom");
stack.Push("Amy");
stack.Push("Katie");
stack.Push("Selena");
stack.Push("Taylor");
stack.Push("Justin");
Console.WriteLine("Stack...");
foreach(Object ob in stack) {
Console.WriteLine(ob);
}
Console.WriteLine("\nSynchronize access...");
lock(stack.SyncRoot) {
foreach(Object ob in stack) {
Console.WriteLine(ob);
}
}
}
}출력 결과
Stack... Justin Taylor Selena Katie Amy Tom Philips Tim Jacob Synchronize access... Justin Taylor Selena Katie Amy Tom Philips Tim Jacob
핵심 정리 및 추가 팁
SyncRoot속성은 컬렉션 자체를 잠그는 것이 아니라, 동기화에 사용되는 전용 객체를 반환합니다.lock블록의 범위는 실제로 공유 데이터에 접근하는 최소한의 영역으로 유지하는 것이 성능 면에서 좋습니다.- .NET 2.0 이후 환경에서는 타입 안전성이 보장되는 제네릭 컬렉션(
Stack<T>)을 사용하고, 멀티스레드 시나리오에서는System.Collections.Concurrent네임스페이스의ConcurrentStack<T>를 활용하는 것이 더 권장됩니다.
이처럼 lock 문과 SyncRoot 속성을 조합하면 레거시 Stack 컬렉션에서도 스레드 간 충돌 없이 안전하게 데이터를 처리할 수 있습니다.