본문 바로가기
C#

[C#] ReadWriteLock 구현

by 소리쿤 2023. 4. 5.
	class Program
	{
		static BitFlagLock locked = new BitFlagLock();
		static int number = 0;

		const int offset = 100000;

		static void Main(string[] args)
		{
			Task t1 = new Task(Thread1);
			Task t2 = new Task(Thread2);

			t1.Start();
			t2.Start();

			Task.WaitAll(t1, t2);

			Console.WriteLine(number);
		}

		static void Thread1()
		{
			for (int i = 0; i < offset; i++)
			{
				locked.WriteLock();
				locked.WriteLock();
				number++;
				locked.WriteUnlock();
				locked.WriteUnlock();
			}
		}

		static void Thread2()
		{
			for (int i = 0; i < offset; i++)
			{
				locked.WriteLock();
				number--;
				locked.WriteUnlock();
			}
		}
	}

	// Unused (1) WriteThreadID (15) ReadCount (16) 
	class BitFlagLock
	{
		const int EMPTY_MASK = 0x00000000;

		const int WRITE_MASK = 0x7FFF0000;
		const int READ_MASK = 0x0000FFFF;

		const int offset = 5000;

		int flag = EMPTY_MASK;
		int writerCount = 0;

		private int GetCurrentWriterThreadID()
		{
			int writerThreadId = (flag & WRITE_MASK) >> 16;
			return writerThreadId;
		}

		private bool IsCurrentWriterThread(Thread newWriter)
		{
			return GetCurrentWriterThreadID() == newWriter.ManagedThreadId;
		}

		// WriteLock 상태에서 WriteLock을 지원함
		public void WriteLock()
		{
			if(IsCurrentWriterThread(Thread.CurrentThread))
			{
				writerCount++;
				return;
			}

			while (true)
			{
				for(int i = 0; i < offset; i++)
				{
					// 성공 시 자신의 스레드 아이디를 Write Bit에 새기고 리턴
					if (Interlocked.CompareExchange(ref flag, (Thread.CurrentThread.ManagedThreadId << 16) & WRITE_MASK, EMPTY_MASK) == EMPTY_MASK)
					{
						writerCount = 1;
						return;
					}
				}

				Thread.Yield();
			}
		}

		public void WriteUnlock()
		{
			int lockCount = --writerCount;
			if (lockCount == 0)
			{
				Interlocked.Exchange(ref flag, EMPTY_MASK);
			}	
		}

		public void ReadLock()
		{
			while(true)
			{
				for(int i = 0; i < offset; i++)
				{
					int expectedReadCount = flag & READ_MASK; 

					// Writer 유저 ID가 존재한다면 성공할 수 없음
					// 또한, Read가 16 비트 범위 밖으로 넘어선다면 성공할 수 없다.

					// Writer가 없고, Read 비트 범위 밖으로 나가지 않는다면,  ReadCount++ 시켜 flag에 삽입
					if (Interlocked.CompareExchange(ref flag, expectedReadCount + 1, expectedReadCount) == expectedReadCount)
						return;
				}

				Thread.Yield();
			}
		}

		public void ReadUnlock()
		{
			Interlocked.Decrement(ref flag);
		}
	}

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

[C#] ThreadLocalStorage?  (0) 2023.04.06
[C#] 공변성, 반공변성  (0) 2023.04.04
[C#] SpinLock 구현  (0) 2023.03.29
[C#] IComparable과 IComparer  (0) 2023.03.26
[C#] Dispose 패턴  (0) 2023.03.23