7.4 ICollection 인터페이스
ICollection 인터페이스는 IEnumerable를 상속하기 때문에 기본적으로 나열의 속성을 가지고 있습니다. 그리고, Collection이라는 말 뜻 그대로 집합적인 개념을 가지고 있습니다. 다음은 ICollection의 상속 구조를 보여주고 있습니다.
그림 7-3 ICollection 인터페이스의 상속 구조
ICollection 인터페이스에서 제공하는 기능은 집합적인 개념과 동기화 지원을 위한 기능들입니다. C#의 컬렉션류라면 당연히 상속해야 하는 아주 대표적인 인터페이스입니다. 위의 상속 구조에서 알 수 있듯이 ICollection은 IEnumerable을 상속하였다는 것도 잊지 마시기 바랍니다. 다음은 ICollection에서 이용할 수 있는 속성과 메서드입니다.
▣ ICollection 인터페이스 속성 |
□ int Count
· 컬렉션의 객체 수를 반환한다. 즉, 컬렉션 내에 얼마나 많은 객체가 있는지 알아낸다.
□ bool IsSynchronized
· 다중 스레드된 액세스를 위해 컬렉션에 대한 액세스(Access)를 동기화한 경우 true를 반환한다. 컬렉션이 동기화되는지에 따라서 쓰레드 안전성이 있는지 검사한다.
□ object SyncRoot
· SyncRoot 속성은 스레드에서 컬렉션에 대한 액세스를 동기화하기 위해 사용할 수 있는 객체를 반환한다.
· CLR은 자동으로 어떤 .NET 형식 인스턴스라도 동기화루트(SyncRoot)가 되도록 한다.
· SyncRoot는 하나 이상의 코드 문장이 동시에 한 스레드에 의해서만 실행되는 것을 확실하게 하기 위해 잠그거나 해제할 수 있다.
· ICollection.SyncRoot를 실행하면 항상 적절한 SyncRoot 객체를 반환한다. |
▣ ICollection 인터페이스 메서드 |
□ void CopyTo(Array array, int index)
· 지정한 배열 위치부터 컬렉션의 요소(Element)를 배열로 복사한다. |
다음의 예제는 컬렉션에 접근하는 방법을 보여주고 있습니다. 물론, 배열에 접근하고 있지만 배열은 기본적으로 Array 클래스를 상속하고 있기 때문에 IColleciton을 구현하고 있는 상태입니다.
& |
ICollectionSyncTest.cs |
Ü ICollection을 테스트하는 예제 |
using System; using System.Collections; using System.Threading;
public class ICollectionSync{ private Int32[ ] intArr = {1,2,3,4, 5, 6, 7, 8, 9, 10}; public void SyncRun(){ ICollection c = intArr;
//배열은 Array을 상속 -> IColleciton으로 캐스팅 가능 IEnumerator e = c.GetEnumerator(); while(e.MoveNext()) { Console.Write((int)e.Current +"\t"); try{ Thread.Sleep(20); }catch{ } } Console.WriteLine("요소갯수:{0} ", c.Count); } } //class
public class ICollectionSyncTest{ public static void Main() { ICollectionSync ics = new ICollectionSync(); //3개의 스레드 생성 new Thread(new ThreadStart(ics.SyncRun)).Start(); new Thread(new ThreadStart(ics.SyncRun)).Start(); new Thread(new ThreadStart(ics.SyncRun)).Start(); } //main } //class |
C:\C#Example\07>csc ICollectionSyncTest.cs
C:\C#Example\07>ICollectionSyncTest
1 1 1 2 2 2 3 3 3 4
4 4 5 5 5 6 6 6 7 7
7 8 8 8 9 9 9 10 10 10
요소갯수:10
요소갯수:10
요소갯수:10 |
ICollectionSync 클래스의 멤버필드로 Int32형 배열을 선언한 후 SyncRun() 메서드 내에서 ICollecton으로 캐스팅하고 있습니다.
private Int32[ ] intArr = {1,2,3,4, 5, 6, 7, 8, 9, 10};
ICollection c = intArr; // 배열을 ICollection 인터페이스로 캐스팅
생성된 ICollection c에서 Enumerator를 얻어내기 위해서 GetEnumerator()를 호출한 후 열거자를 이용해서 모든 데이터를 출력하고 있습니다.
IEnumerator e = c.GetEnumerator();
while(e.MoveNext()) {
Console.Write((int)e.Current +"\t");
}
위의 예제에서는 3개의 스레드를 생성하여 각각 SynRun() 메서드를 스레드에 할당합니다. 그리고, Start() 메서드를 호출하여 3개의 스레드를 실행하고 있습니다.
new Thread(new ThreadStart(ics.SyncRun)).Start();
new Thread(new ThreadStart(ics.SyncRun)).Start();
new Thread(new ThreadStart(ics.SyncRun)).Start();
SynRun() 메서드 내부에서는 ICollectionSync 클래스 내의 배열에 동시에 접근하고 있습니다. 하지만, 여기서는 어떠한 동기화도 처리하지 않았습니다.
그림 7-4 컬렉션에서의 동기화 문제
동기화를 보장하고 있지 않기 때문에 각각의 스레드들이 동시에 접근하고 있습니다. 하지만, 이러한 접근에 동기화를 가미하면 전혀 다른 결과를 가져 올 수 있습니다.
다음의 예제는 앞의 예제에서 동기화 부분을 추가한 예제입니다.
& |
ICollectionSyncTest1.cs |
Ü ICollection의 동기화를 테스트하는 예제 |
using System; using System.Collections; using System.Threading;
public class ICollectionSync{ private Int32[ ] intArr = {1,2,3,4, 5, 6, 7, 8, 9, 10}; public void SyncRun(){ ICollection c = intArr;
//배열은 Array을 상속 -> IColleciton으로 캐스팅 가능 lock(c.SyncRoot){ IEnumerator e = c.GetEnumerator(); while(e.MoveNext()) { Console.Write((int)e.Current +"\t"); try{ Thread.Sleep(20); }catch{ } } Console.WriteLine("요소갯수:{0} ", c.Count); } //lock } } //class
public class ICollectionSyncTest1{ public static void Main() { ICollectionSync ics = new ICollectionSync(); new Thread(new ThreadStart(ics.SyncRun)).Start(); new Thread(new ThreadStart(ics.SyncRun)).Start(); new Thread(new ThreadStart(ics.SyncRun)).Start(); } //main } //class |
C:\C#Example\07>csc ICollectionSyncTest1.cs
C:\C#Example\07>ICollectionSyncTest1
1 2 3 4 5 6 7 8 9 10
요소갯수:10
1 2 3 4 5 6 7 8 9 10
요소갯수:10
1 2 3 4 5 6 7 8 9 10
요소갯수:10 |
두 예제의 결과를 비교해 보시면 동기화에 대한 느낌을 정확하게 이해하실 수 있을 것입니다. 동기화란 하나의 자원을 여러 스레드가 이용할 때 동시에 자원을 이용하는 것이 아니라 하나의 스레드가 자원을 점유하고 있으면 다른 스레드들은 기다리는 원리입니다. 그래서, 차례대로 일처리가 되는 것입니다.
ICollection 인터페이스에서는 이러한 동기화를 위한 자원 관리의 효율을 위해서 SyncRoot라는 속성을 제공해 주고 있습니다. 다음과 같이 lock 키워드를 이용하시면 공유자원에 순서대로 접근하게 해줍니다.
lock(c.SyncRoot){
// 공유자원 c를 이용하여 작업
}
동기화에 관련된 보다 자세한 사항을 알고자 한다면 Thread 부분을 참고하시기 바랍니다. |