2022년 3월 7일 월요일

[c# ReaderWriterLock]

 재귀적LOCK 비허용 버전



using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace ServerCore
{
    /// <summary>
    /// 재귀적 락을 허용할지 (No)(쉽게하기위해 No로 구현)
    /// 스핀락 정책 (5000번-> Yield)
    /// </summary>
    class Lock
    {

        /// <summary>
        /// 총 32비트
        /// 0000 0000 0000 0000 16
        /// 0000 0000 0000 0000 16
        /// </summary>
        const int EMPTY_FLAG = 0x00000000;



        /// <summary>
        /// 0111 1111 1111 1111
        /// 0000 0000 0000 0000///여기사용
        /// </summary>
        const int WRITE_MASK = 0x7FFF0000;
       



        const int READ_MASK = 0x0000FFFF;
        /// <summary>
        /// 0000 0000 0000 0000 ///여기사용
        /// 1111 1111 1111 1111
        /// </summary>

        const int MAX_SPIN_COUNT = 5000;

       
       

        int _flag = EMPTY_FLAG;
        /// <summary>
        /// // int 32비트
        /// [Unused(1)] [WriteThreadid(15)][ReadCount(16)]
        /// 첫비트는 사용안함(Unused)
        /// WriteThreadid(15) 획득한 thread 기록용
        /// ReadCount(16) readLock을 획득했을때 카운팅용
        /// 시작값은 0으로 맞춰준다
        /// </summary>




        public void WriteLock()
        {
            // 아무도 WriteLock or ReadLock을 획득하고 있지 않을 때, 경합해서 소유권을 얻는다

            ///Thread.CurrentThread.ManagedThreadId 를 ->[WriteThreadid(15)] 여기에 넣기위해
            /// 16비트만큼 밀어준다 <<16
            /// 혹시 모르니까 & WRITE_MASK 해서 초과하는 값이 있으면 0으로 밀어주기
            /// [Unused(1)] 이부분과 [ReadCount(16)] 이부분은 0으로 변경해주기!
            int desired = (Thread.CurrentThread.ManagedThreadId << 16) & WRITE_MASK;
           
            while (true)
            {
                for (int i = 0; i < MAX_SPIN_COUNT; i++)
                {
                    // 시도를 해서 성공하면 return

                    ///Interlocked.CompareExchange(ref _flag, desired, EMPTY_FLAG) 이 부분은 이전값을 리턴하므로
                    ///EMPTY_FLAG를 리턴값으로 받으면 정확히 비어있던 객체가 맞으니 통과!

                    ///승자의 desired값이 _flag에 들어가게된다
                    ///그러면 다른애들은 들어올수없다! (EMPTY_FLAG)와 같을수없음!

                    if (Interlocked.CompareExchange(ref _flag, desired, EMPTY_FLAG) == EMPTY_FLAG)
                        return;

                    ///의사코드 모습
                    ///실제로는 작동못함 원자성실패때문!
                    /// if (_flag == EMPTY_FLAG) // 두개로 나뉘어 져있어서 동시에 통과해서 에러날가능성 다분!
                    ///        _flag = desired; //카운트 ++ 문제와 마찬가지로 여러가지 desired값이 _flag에 들어가게됨!!
                    ///의사코드 모습


                }

                Thread.Yield();
            }
        }
        public void WriteUnLock()
        {
            Interlocked.Exchange(ref _flag, EMPTY_FLAG);///EMPTY_FLAG로 비워둔다(초기상태로 변경)
        }
        public void ReadLock()
        {
            ///아무도 WriteLock을 획득하고 있지않으면, ReadCount를 1 늘린다.
            while (true)
            {
                for (int i = 0; i < MAX_SPIN_COUNT; i++)
                {


                    ///처음보면 이해가 존나안감;; 나도 이해가안감;
                    /// LockFree 프로그램의 기초라고 할수있으니 이해해보도록 노력하자


                    /// 경우 1  writeLock을 누가 잡고있다면
                    ///
                    /// [WriteThreadid(15)] 이부분이 0이 아니기때문에
                    /// expected 값이 맞지 않으니 무조건 false ! ---> _flag != _flag & READ_MASK

                    /// 경우 2 writelock은 안쓰고 있는데 동시에 여러명이 ReadLock을 획득하려고 할 때!
                    ///
                    /// 처음 expected를 뽑아올때는 전부 성공할것이다
                    /// 하지만 가장 처음 들어온 녀석만 +1을 성공하고
                    /// 나머지 녀석들은 expected와 같지않으니(예상한값과 다름) +1을 하지못하고 false
                    ///

                    /// A,B 두녀석이 ReadLock획득을 위해 경쟁하고 있다고 치면 이런모양
                    /// 1. A(0) B(0)
                    /// 2. A(0->1) B(0->1)
                    /// 3. 빠른사람이 성공
                    /// 3.1 A가 빨랏으면 B(1->1)이런모습이 되서 실패됨
                    /// 3.1 B가 빨랏으면 A(1->1)이런모습이 되서 실패됨


                    int expected = (_flag & READ_MASK);
                    if (Interlocked.CompareExchange(ref _flag, expected+1, expected) == expected)
                        return;
                   
                   
                    ///의사코드
                    ///원자성 때문에 이것도 문제발생!!
                    //if ((_flag & WRITE_MASK) == 0)
                    //{
                    //    ///아무도 writeLock을 획득하고있지않음!
                     //   _flag = _flag + 1;
                      //  return;
                    //}
                    ///의사코드
                }

                Thread.Yield();
            }
        }
        public void ReadUnLock()
        {
            Interlocked.Decrement(ref _flag);
        }

    }
}





재귀락 허용버전


using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace ServerCore
{
    /// <summary>
    /// 재귀적 락을 허용할지 (Yes) -> WriteLokc->WriteLock OK, WriteLock->ReadLock OK, ReadLock->WriteLock No
    /// 스핀락 정책 (5000번-> Yield)
    /// </summary>
    class Lock
    {

        /// <summary>
        /// 총 32비트
        /// 0000 0000 0000 0000 16
        /// 0000 0000 0000 0000 16
        /// </summary>
        const int EMPTY_FLAG = 0x00000000;



        /// <summary>
        /// 0111 1111 1111 1111
        /// 0000 0000 0000 0000///여기사용
        /// </summary>
        const int WRITE_MASK = 0x7FFF0000;
       



        const int READ_MASK = 0x0000FFFF;
        /// <summary>
        /// 0000 0000 0000 0000 ///여기사용
        /// 1111 1111 1111 1111
        /// </summary>

        const int MAX_SPIN_COUNT = 5000;

       
       

        int _flag = EMPTY_FLAG;
        /// <summary>
        /// // int 32비트
        /// [Unused(1)] [WriteThreadid(15)][ReadCount(16)]
        /// 첫비트는 사용안함(Unused)
        /// WriteThreadid(15) 획득한 thread 기록용
        /// ReadCount(16) readLock을 획득했을때 카운팅용
        /// 시작값은 0으로 맞춰준다
        /// </summary>
        ///

        int _writeCount = 0;
        /// <summary>
        /// 멀티스레드 문제가 있을수 없으므로 flag로 뺄필요없음!!
        /// 상호배타적인 writeLock의 숫자를 관리하기위해 추가(재귀락 허용버전)
        /// </summary>




        public void WriteLock()
        {

            ///동일 쓰레드가 writeLock을 이미 획득하고 있는지 확인
            int lockThreadId = (_flag & WRITE_MASK) >> 16;
            if (Thread.CurrentThread.ManagedThreadId == lockThreadId)
            {
                ///획득하고 있다면 count 를 1만큼 늘이고 리턴 하지만 이것도 원자성 먹나?
                _writeCount++;
                return;
            }


            // 아무도 WriteLock or ReadLock을 획득하고 있지 않을 때, 경합해서 소유권을 얻는다

            ///Thread.CurrentThread.ManagedThreadId 를 ->[WriteThreadid(15)] 여기에 넣기위해
            /// 16비트만큼 밀어준다 <<16
            /// 혹시 모르니까 & WRITE_MASK 해서 초과하는 값이 있으면 0으로 밀어주기
            /// [Unused(1)] 이부분과 [ReadCount(16)] 이부분은 0으로 변경해주기!
            int desired = (Thread.CurrentThread.ManagedThreadId << 16) & WRITE_MASK;
           
            while (true)
            {
                for (int i = 0; i < MAX_SPIN_COUNT; i++)
                {
                    // 시도를 해서 성공하면 return

                    ///Interlocked.CompareExchange(ref _flag, desired, EMPTY_FLAG) 이 부분은 이전값을 리턴하므로
                    ///EMPTY_FLAG를 리턴값으로 받으면 정확히 비어있던 객체가 맞으니 통과!

                    ///승자의 desired값이 _flag에 들어가게된다
                    ///그러면 다른애들은 들어올수없다! (EMPTY_FLAG)와 같을수없음!

                    if (Interlocked.CompareExchange(ref _flag, desired, EMPTY_FLAG) == EMPTY_FLAG)
                    {
                        _writeCount++;
                        return;
                    }

                    ///의사코드 모습
                    ///실제로는 작동못함 원자성실패때문!
                    /// if (_flag == EMPTY_FLAG) // 두개로 나뉘어 져있어서 동시에 통과해서 에러날가능성 다분!
                    ///        _flag = desired; //카운트 ++ 문제와 마찬가지로 여러가지 desired값이 _flag에 들어가게됨!!
                    ///의사코드 모습


                }

                Thread.Yield();
            }
        }
        public void WriteUnLock()
        {

            int lockCount = --_writeCount;

            if (lockCount == 0)
            {
                ///실행한 만큼 짝이 맞아야지만 실행이되서 Lock이 풀리는 구조!
                Interlocked.Exchange(ref _flag, EMPTY_FLAG);///EMPTY_FLAG로 비워둔다(초기상태로 변경)
            }
        }
        public void ReadLock()
        {


            int lockThreadId = (_flag & WRITE_MASK) >> 16;
            if (Thread.CurrentThread.ManagedThreadId == lockThreadId)
            {
                ///획득하고 있다면 count 를 1만큼 늘이고 리턴 하지만 이것도 원자성 먹나?
                Interlocked.Increment(ref _flag);
                return;
            }


            ///아무도 WriteLock을 획득하고 있지않으면, ReadCount를 1 늘린다.
            while (true)
            {
                for (int i = 0; i < MAX_SPIN_COUNT; i++)
                {


                    ///처음보면 이해가 존나안감;; 나도 이해가안감;
                    /// LockFree 프로그램의 기초라고 할수있으니 이해해보도록 노력하자


                    /// 경우 1  writeLock을 누가 잡고있다면
                    ///
                    /// [WriteThreadid(15)] 이부분이 0이 아니기때문에
                    /// expected => writeThread값이 0 이니까 지금의 _flag(writeLock이 잡힌)과 맞지 않으니 통과안됨!
                    /// expected 값이 맞지 않으니 무조건 false ! ---> _flag != _flag & READ_MASK

                    /// 경우 2 writelock은 안쓰고 있는데 동시에 여러명이 ReadLock을 획득하려고 할 때!
                    ///
                    /// 처음 expected를 뽑아올때는 전부 성공할것이다
                    /// 하지만 가장 처음 들어온 녀석만 +1을 성공하고 (expected 값이 _flag와 같음! +1이 안됫으니까)
                    /// 나머지 녀석들은 expected와 같지않으니(예상한값과 다름) +1을 하지못하고 false
                    ///

                    /// A,B 두녀석이 ReadLock획득을 위해 경쟁하고 있다고 치면 이런모양
                    /// 1. A(0) B(0)
                    /// 2. A(0->1) B(0->1)
                    /// 3. 빠른사람이 성공
                    /// 3.1 A가 빨랏으면 B(1->1)이런모습이 되서 실패됨
                    /// 3.1 B가 빨랏으면 A(1->1)이런모습이 되서 실패됨


                    int expected = (_flag & READ_MASK); ///현재상황에서 보는값은 READ_MASK를 씌운값이다(뒤에 FFFF이것만 본다)
                    ///WriteFlag에 해당하는값은 0으로 밀어버린것
                    ///이말은 writeFlag가 전부 0이라는 소리다! == >  (_flag & WRITE_MASK) == 0 이부분임!!!

                    /// (_flag & WRITE_MASK) == 0 이부분은 완전히 사라진 이유는 READ_MASK를 씌우면 0이되니까!

                    ///정리하자면
                    ///원하는 값 expected는
                    ///WRITE_MASK는 0 , READ_MASK는 READ_MASK를 씌운값
                    ///그래서 WRITE_MASK가 없다면 READ_MASK에 1을 더해주고 끝!

                    ///[Unused(1)] [WriteThreadid(15)][ReadCount(16)]

                    if (Interlocked.CompareExchange(ref _flag, expected+1, expected) == expected)
                        return;
                   
                   
                    ///의사코드
                    ///원자성 때문에 이것도 문제발생!!
                    //if ((_flag & WRITE_MASK) == 0)
                    //{
                    //    ///아무도 writeLock을 획득하고있지않음!
                     //   _flag = _flag + 1;
                      //  return;
                    //}
                    ///의사코드
                }

                Thread.Yield();
            }
        }
        public void ReadUnLock()
        {
            Interlocked.Decrement(ref _flag);
        }

    }
}

댓글 없음:

댓글 쓰기

git rejected error(feat. cherry-pick)

 문제 아무 생각 없이 pull을 받지않고 로컬에서 작업! 커밋, 푸시 진행을 해버렷다. push에선 remote와 다르니 당연히 pull을 진행해라고 하지만 로컬에서 작업한 내용을 백업하지 않고 진행하기에는 부담스럽다(로컬작업 유실 가능성) 해결하려...