-
.NET 5.0까지의 Random
기본적으로 현재 최신버전인 .NET 5.0의 Random클래스는 ThreadSafe하지 않다.
구현 내용을 보면 다음과 같이 멤버 변수로 ImplBase타입을 가지고 있는데,
public partial class Random { private readonly ImplBase _impl; .....생략
ImplBase를 상속 받은 클래스 또한 멤버 변수를 가지고 있고, 이 멤버변수를 이용해 Random관련 연산을 진행하게 된다.
public partial class Random { internal sealed class XoshiroImpl : ImplBase { private uint _s0, _s1, _s2, _s3; // 멤버 변수를 이용해 연산 .....생략 internal uint NextUInt32() { uint s0 = _s0, s1 = _s1, s2 = _s2, s3 = _s3; uint result = BitOperations.RotateLeft(s1 * 5, 7) * 9; uint t = s1 << 9; s2 ^= s0; s3 ^= s1; s1 ^= s2; s0 ^= s3; s2 ^= t; s3 = BitOperations.RotateLeft(s3, 11); _s0 = s0; _s1 = s1; _s2 = s2; _s3 = s3; return result; } .....생략
위와 같은 구현이기에 당연히 다중 스레드 상황에서 하나의 Random 객체를 공유하여 사용하면 문제가 발생하게 되고,
우리는 다중 스레드에 안전하게 사용하기 위해 Random 클래스를 한번 더 Wapping한 클래스를 만들어 사용하게 된다.
그 방식은 크게 두가지 방법 중 선택하게 된다.
- 공유 하는 Random객체의 Next() 호출 시 lock을 거는 방법.
- TLS(Thread Local Storage)를 통해 Thread 마다 별도의 Random 클래스를 생성하여 사용하는 방법.
1번의 경우 빈번하게 Next() 호출 시 부담이 될 수 있기에 일반적으로 2번과 같은 TLS 방식이 선호되어 왔다.
2번 방식의 Wapper클래스 구현은 다음과 같다.
public static class ThreadSafeRandom { public static int Next(int min, int max) { if (min > max) { throw new ArgumentException($"[RandomWapper::Next] min:{min} max:{max}"); } return TlsRandom.Instance.Next(min, max); // 스레드마다 각각 가지고 있는 Random 객체를 사용. } .....생략 private static class TlsRandom { private static readonly ThreadLocal<System.Random> Random = new ThreadLocal<System.Random>(() => { int seed = Environment.TickCount; return new System.Random(seed); }); internal static System.Random Instance => Random.Value; } }
.NET 6.0에서의 Random
위와 같이 번거롭게 Warpper클래스를 만드는 수고를 .NET 6.0에서는 조금 덜 수 있을 것 같다.
다음은 .NET 6.0에서의 Random 클래스이다.
public partial class Random { .....생략 public static Random Shared { get; } = new ThreadSafeRandom(); .....생략 private sealed class ThreadSafeRandom : Random { [ThreadStatic] private static XoshiroImpl? t_random; // TLS로 random 값을 가진다. public ThreadSafeRandom() : base(isThreadSafeRandom: true) { } private static XoshiroImpl LocalRandom => t_random ?? Create(); [MethodImpl(MethodImplOptions.NoInlining)] private static XoshiroImpl Create() => t_random = new(); public override int Next(int minValue, int maxValue) { if (minValue > maxValue) { ThrowMinMaxValueSwapped(); } int result = LocalRandom.Next(minValue, maxValue); AssertInRange(result, minValue, maxValue); return result; }
Random클래스의 Shared 호출 시 ThreadSafeRandom의 객체를 사용하게 되고 내부적으로는 스레드마다 각각 가지고 있는 LocalRandom값을 사용하여 서로 다른 Random클래스의 Next()함수를 호출하게 된다.
즉, 우리가 일반적으로 만들어 사용하던 TLS 방식을 내장해준 것이다.
static void Main(string[] args) { int result = Random.Shared.Next(0, 1000); }
사용법 또한 위와 같이 매우 간단해질 것이다.
[참고]
.NET 6.0 (preview)'Programming > C#' 카테고리의 다른 글
[.NET / C#] String.Create<TState>() (0) 2022.01.23 [.NET / C#] List의 Enumerator는 왜 public struct 일까? (0) 2022.01.17 [.NET / C#] async & await 2 - async와 await 키워드 (0) 2022.01.16 [.NET / C#] async & await 1 - async와 await 키워드 (0) 2021.06.02 [.NET / C#] Generic Collection에서 enum의 사용은 boxing을 발생 시키는가? (0) 2021.05.06 댓글