2022년 3월 11일 금요일

c# null체크 하는 방식

 


///안좋은 예
            ///

            ///if (_socket == null)
            ///    return;
            ///_socket = null;


///멀티 스레드 환경일때 예

int _disconnected = 0;

///_disconnected 를 1로 변경하고 원래값을 반환한다(return _disconnected ->0)
///_disconnected 를 1로 변경하고 원래값을 반환한다(return _disconnected -> 1)
///1이 되면 리턴(2번이상 실행됬다는 소리!!)
if (Interlocked.Exchange(ref _disconnected, 1) == 1)
                return;


근데 언제 다시 0으로 만들어줘서 _disconnected가 다시 0 되는거지???
new Session이라서 처음에는 무조건 0!

만약 돌려쓴다면 다시 0으로 돌려줘야함

Interlocked.Exchange(ref _disconnected, 0)//lock 해제






[c# 간단한 비동기 TCP]


비동기

더미 클라이언트

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

/// <summary>
/// 유니티 대신해서 테스트 하기위한 클라이언트
/// 테스팅 환경 구축용
/// </summary>
///
namespace DummyClient
{
    class Program
    {
        static void Main(string[] args)
        {

            /// DNS (Domain Name System)
            /// 172.1.2.3(실제로 넣으면 문제가됨!) 하드코딩 X!
            /// 하지만 도메인 등록이라면 문제가 없다
            /// www.rookiss.com -> 123.123.123.12
            string host = Dns.GetHostName();
            IPHostEntry ipHost = Dns.GetHostEntry(host);
            IPAddress ipAddr = ipHost.AddressList[0];
            IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);



            while (true)
            {
                ///휴대폰 설정
                Socket socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);


                try
                {
                    ///문지기한테 입장 문의
                    socket.Connect(endPoint);
                    Console.WriteLine($"Connected To{socket.RemoteEndPoint.ToString()}");


                    ///보낸다
                    byte[] sendBuff = Encoding.UTF8.GetBytes("Hello World!");
                    int sendBytes = socket.Send(sendBuff);



                    ///받는다
                    byte[] recvBuff = new byte[1024];
                    int recvBytes = socket.Receive(recvBuff);

                    string receiveData = Encoding.UTF8.GetString(recvBuff, 0, recvBytes);

                    Console.WriteLine($"From server{receiveData }");

                    ///나간다
                    socket.Shutdown(SocketShutdown.Both);
                    socket.Close();

                }
                catch (Exception e)
                {
                    Console.WriteLine(e.ToString());
                }

                Thread.Sleep(100);
            }

        }
    }
}






비동기 서버 Listener

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace ServerCore
{
    class Listener
    {
        Socket _listenSocket;

        /// Accept가 완료되면 어떻게 처리할건지
        Action<Socket> _onAcceptHandler;
       


        public void Init(IPEndPoint endPoint,Action<Socket> onAcceptHandler)
        {
            ///문지기 만들기(핸드폰만들기)
            _listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

            _onAcceptHandler += onAcceptHandler;


            ///문지기 교육
            _listenSocket.Bind(endPoint);

            ///영업 시작
            ///backlog : 최대 대기수
            /// 문지기가 안내하기 전까지 대기하는 수
            /// 만약 이 수가 넘어가는 사람이 문의하면 바로 fail이 뜬다
            _listenSocket.Listen(10);

            /// 한번만 만들어주면 계속 재활용 할수있는 어마어마한 장점이 있음
            SocketAsyncEventArgs args = new SocketAsyncEventArgs();
            args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);///나중에라도 완료되면 여기 이벤트로 연락오게하기!
            RegisterAccept(args);///최초로 낚시대를 강물에 던짐!
        }








        void RegisterAccept(SocketAsyncEventArgs args)
        {

            ///중요!
            ///다시 돌아올때 비어있는 args여야한다!!!
            args.AcceptSocket = null;


            /// 비동기! 동시에 처리안되고 나중에 처리될수있음!!!
            ///성공하든 아니든 상관없이 바로 리턴을 하고 본다(문제가 될수있음)
            ///로그인에 실패했는데 리턴때릴수있음
            ///비동기 계열을 사용하면 대화를 해야함(결과에 대해서)
            ///

            ///당장 완료한다는 보장은 없고 요청을 하긴 할꺼다
            ///말그대로 등록을 한거라고 보면됨
            bool pending = _listenSocket.AcceptAsync(args);
            if (pending == false)
            {
                ///비동기로 실행했지만 운좋게 완료된 케이스
                ///낚시대를 강물에 던지자마자 물고기가 잡힌 케이스
                OnAcceptCompleted(null,args);
            }
            else
            {
                ///pending true
                /// 나중에 args로 Completed 로 연락이 올것이다.
            }

        }

        void OnAcceptCompleted(object sender, SocketAsyncEventArgs args)
        {
            if (args.SocketError == SocketError.Success)
            {
                ///에러 없이 잘 처리 됫다(실제로 accept를 했다)
                /// 유저가 커넥트 연결이 와서 실제로 accept까지 했다면
                ///TODO
                _onAcceptHandler.Invoke(args.AcceptSocket);///완료됬으면 _onAccepHandler 실행!

            }
            else
            {
                Console.WriteLine(  args.SocketError.ToString());
            }


            ///모두 완료됬으니 다음 아이를 위해서 다시 등록!!
            ///다음 낚시대 던지기
            RegisterAccept(args);
        }

    }
}


비동기 서버 Main


using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;



/// <summary>
/// 소켓 프로그래밍 기초
///
/// 메모장 코드를 메인안에 때려박아넣으면 안됨!
///어짜피 커질꺼니까 처음부터 분리하는 습관을 들여야함!
///
///
/// </summary>

namespace ServerCore
{
    class Program
    {
        static Listener _listener = new Listener();

        static void OnAcceptHandler(Socket clientSocket)
        {

            try
            {
               
                byte[] recvBuff = new byte[1024];
                int recvBytes = clientSocket.Receive(recvBuff);
                string recvData = Encoding.UTF8.GetString(recvBuff, 0, recvBytes);
                Console.WriteLine($"From Client[{recvData}]");

                /// 보낸다
                byte[] sendBuff = Encoding.UTF8.GetBytes("Welcome to MMORPG SERVER!");
                clientSocket.Send(sendBuff);

                ///쫒아낸다
                clientSocket.Shutdown(SocketShutdown.Both);
                clientSocket.Close();
                /// 받는다
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }

           
        }


        static void Main(string[] args)
        {
            /// DNS (Domain Name System)
            /// 172.1.2.3(실제로 넣으면 문제가됨!) 하드코딩 X!
            /// 하지만 도메인 등록이라면 문제가 없다
            /// www.rookiss.com -> 123.123.123.12
            string host = Dns.GetHostName();
            IPHostEntry ipHost = Dns.GetHostEntry(host);
            IPAddress ipAddr =  ipHost.AddressList[0];
            IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);


            ///혹시나 누가 들어오면 OnAcceptHandler에 전달해줘
            _listener.Init(endPoint, OnAcceptHandler);
            Console.WriteLine("Listening...");

            while (true)
            {

            }
        }
    }
}




 




2022년 3월 10일 목요일

[c# 간단한 TCP 서버,클라이언트]

 

간단한 서버

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;



/// <summary>
/// 소켓 프로그래밍 기초
/// </summary>

namespace ServerCore
{
    class Program
    {
        static void Main(string[] args)
        {
            /// DNS (Domain Name System)
            /// 172.1.2.3(실제로 넣으면 문제가됨!) 하드코딩 X!
            /// 하지만 도메인 등록이라면 문제가 없다
            /// www.rookiss.com -> 123.123.123.12
            string host = Dns.GetHostName();
            IPHostEntry ipHost = Dns.GetHostEntry(host);
            IPAddress ipAddr =  ipHost.AddressList[0];
            IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);



            ///식당주소 -> ipAddr
            ///식당 정문,후문 인지 ->7777 (port)


            ///문지기 만들기(핸드폰만들기)
            Socket listenSocket = new Socket(endPoint.AddressFamily,SocketType.Stream,ProtocolType.Tcp);

            try
            {
                ///문지기 교육시키기(문지기 핸드폰에 주소연동)
                listenSocket.Bind(endPoint);

                ///영업 시작
                ///backlog : 최대 대기수
                /// 문지기가 안내하기 전까지 대기하는 수
                /// 만약 이 수가 넘어가는 사람이 문의하면 바로 fail이 뜬다
                listenSocket.Listen(10);

                while (true)
                {
                    Console.WriteLine("Listening...");

                    /// 손님을 입장시킨다.
                    Socket clientSocket = listenSocket.Accept(); ///손님과 대화할 소켓 생성

                                                                 /// 받는다
                    byte[] recvBuff = new byte[1024];
                    int recvBytes = clientSocket.Receive(recvBuff);
                    string recvData = Encoding.UTF8.GetString(recvBuff, 0, recvBytes);
                    Console.WriteLine($"From Client[{recvData}]");

                    /// 보낸다
                    byte[] sendBuff = Encoding.UTF8.GetBytes("Welcome to MMORPG SERVER!");
                    clientSocket.Send(sendBuff);

                    ///쫒아낸다
                    clientSocket.Shutdown(SocketShutdown.Both);
                    clientSocket.Close();
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }


           
        }
    }
}



간단한 클라이언트

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

/// <summary>
/// 유니티 대신해서 테스트 하기위한 클라이언트
/// 테스팅 환경 구축용
/// </summary>
///
namespace DummyClient
{
    class Program
    {
        static void Main(string[] args)
        {

            /// DNS (Domain Name System)
            /// 172.1.2.3(실제로 넣으면 문제가됨!) 하드코딩 X!
            /// 하지만 도메인 등록이라면 문제가 없다
            /// www.rookiss.com -> 123.123.123.12
            string host = Dns.GetHostName();
            IPHostEntry ipHost = Dns.GetHostEntry(host);
            IPAddress ipAddr = ipHost.AddressList[0];
            IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);





            ///휴대폰 설정
            Socket socket = new Socket(endPoint.AddressFamily,SocketType.Stream,ProtocolType.Tcp);


            try
            {
                ///문지기한테 입장 문의
                socket.Connect(endPoint);
                Console.WriteLine($"Connected To{socket.RemoteEndPoint.ToString()}");


                ///보낸다
                byte[] sendBuff = Encoding.UTF8.GetBytes("Hello World!");
                int sendBytes = socket.Send(sendBuff);



                ///받는다
                byte[] recvBuff = new byte[1024];
                int recvBytes = socket.Receive(recvBuff);

                string receiveData = Encoding.UTF8.GetString(recvBuff, 0, recvBytes);

                Console.WriteLine($"From server{receiveData }");

                ///나간다
                socket.Shutdown(SocketShutdown.Both);
                socket.Close();

            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }


           

           
            ///
        }
    }
}






[c# Interlocked.CompareExchange]

 Interlocked.CompareExchange 설명


값이 같은지 비교하여 같으면 번째 값을 바꿉니다.


[System.CLSCompliant(false)]
public static ulong CompareExchange (ref ulong location1, ulong value, ulong comparand);

매개 변수
location1
UInt64
comparand와 비교하여 바뀔 있는 값을 가진 대상입니다.

value
UInt64
비교 결과가 같은 경우 대상 값을 바꿀 값입니다.

comparand
UInt64
location1의 값과 비교할 값입니다.

2022년 3월 9일 수요일

[c# TLS]

 using System;

using System.Threading;
using System.Threading.Tasks;



/// <summary>
/// TLS 구현
/// 응용사례
/// 일거리가 아주 많이 큐에 저장되어있을때 하나씩만 꺼내서 쓰는게 아니라
/// 한 100개씩 한 뭉텅어리를 뽑아와서 자기만의 공간에 넣어둔다음에
/// TLS에서 사용하는것은 굳이 Lock을 걸지않아도 부담없이 사용가능하다(다른thread라면) 다른Thread는 접근할수없는 공간이니 Lock 자체가 필요없다.
///
/// 결과적으로 공용공간에 접근횟수를 줄일 수 있다.
///
///
/// ex) static [JobQueue] 가 있을때 동시다발적으로 접근이 힘듬( Lock을 해줘야해서)
///
///
/// 뿐만아니라 매우 다양하게 사용됨!( 스레드 전역변수 사용할 때 사용하면 됨!)
/// </summary>

namespace ServerCore
{
    class Program
    {

        ///static string ThreadName;/// 전역으로 사용해서 다른곳에서 변경 할 때 문제가 생길 수 있음

        //static ThreadLocal<string>  ThreadName=new ThreadLocal<string>();/// 스레드마다 접근으로 변경
                                                                         ///전역변수는 전역변수인데 스레드마다 고유한 공간이 생김!
                                                                         ///



        static ThreadLocal<string> ThreadName = new ThreadLocal<string>(()=> { return $"My Name Is {Thread.CurrentThread.ManagedThreadId}";  });/// 만약 생성이 안됫다면 (Value 가 null이라면)
                                                                                                                                                ///$"My Name Is {Thread.CurrentThread.ManagedThreadId}" 이걸로 생성함

        static void WhoAmI()
        {

            bool repeat = ThreadName.IsValueCreated; ///한번도 만든적이 없다면 false로 return
            if(repeat)
                Console.WriteLine(ThreadName.Value + "(repeat)");
            else
                Console.WriteLine(ThreadName.Value);
            //ThreadName.Value = $"My Name Is {Thread.CurrentThread.ManagedThreadId}";///자신의 스레드 영역에서만 이름을 바꾼 상태

            //Thread.Sleep(1000); ///1초동안 시간을 줘서 다른곳에서 고친게 내 스레드에 적용되는지 확인


        }

        static void Main(string[] args)
        {

            ThreadPool.SetMinThreads(1, 1);
            ThreadPool.SetMaxThreads(3, 3);

            Parallel.Invoke(WhoAmI, WhoAmI, WhoAmI, WhoAmI, WhoAmI, WhoAmI, WhoAmI);
            ThreadName.Dispose();
        }
    }
}

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을 진행해라고 하지만 로컬에서 작업한 내용을 백업하지 않고 진행하기에는 부담스럽다(로컬작업 유실 가능성) 해결하려...