In to the C# 2014. 1. 14. 14:09

Thread Pool과 Task

C#에서 Task는 비동기 작업을 나타낸다고 합니다.

Task는 .NET Framework4.0 부터 사용 할 수 있습니다. Task의 작동방식은 .NET Framework에서 관리되고 있는 ThreadPool에서 작동합니다. 그럼 ThreadPooling에 대해서 간단히 알아 보겠습니다.

닷넷 프레임워크로 작성된 어플리케이션은 실행되면서 자동으로 ThreadPool도 생성이 됩니다. ThreadPool을 사용하는 Task와 비동기 타이머 같은 것들을 지원하기 위해서죠. 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
 
namespace ConsoleApplication26
{
  class Program
  {
    static void Main(string[] args)
    {
      int minWorkerThreads = 0;
            int minCompletionPortThreads = 0;
            ThreadPool.GetMinThreads(out minWorkerThreads, out minCompletionPortThreads); 
 
            int maxWorkerThreads = 0;
            int maxCompletionPortThreads = 0;
            ThreadPool.GetMaxThreads(out maxWorkerThreads, out maxCompletionPortThreads); 
 
            Console.WriteLine("Min Worker Threads : {0}, max Worker Threads : {1}", minWorkerThreads, maxWorkerThreads);
            Console.WriteLine("Min CompletionPort Threads : {0}, max CompletionPort Threads : {1}", minCompletionPortThreads, maxCompletionPortThreads);
        }
    }
}


위 코드는 ThreadPool의 최소, 최대 Thread 개수를 구합니다. 실행하면 아래와 같은 결과가 나옵니다.



ThreadPool은 최소 4개부터 최대 1023개까지 작업자 스레드를 갖을 수 있습니다.

CompletionPort Thread는 비동기 I/O스레드의 수 입니다. 비동기 I/O스레드의 수는 4 ~ 1000개 까지 갖을 수 있습니다.


ThreadPool은 언덕등반 알고리즘(Hill Climb Algorithm)에 의해 자동으로 스레드가 생성이 됩니다.

즉, Task를 쓸 경우 Thread의 개수는 닷넷프레임워크가 알아서 조절해 준다는 것이지요.

간단한 Task를 만들어서 ThreadPool이 어떻게 작동하는지 보겠습니다.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication26
{
    class Program
    {
        static List<int> threadIdList = new List<int>();
        static object lockList = new object();

        static void AddBag(int threadId)
        {
            lock (lockList)
            {
                if (!threadIdList.Contains(threadId))
                    threadIdList.Add(threadId);
            }
        }

        static void Print()
        {
            Console.WriteLine("Thread Count : {0}, Thread IDs : {1}", threadIdList.Count, string.Join(",", threadIdList));
        }

        static void Main(string[] args)
        {
            List<Task> taskList = new List<Task>();
            for (int i = 0; i < 100; i++)
            {
                Task t = new Task(() =>
                {
                    AddBag(Thread.CurrentThread.ManagedThreadId);
                    Thread.Sleep(1000);
                });

                taskList.Add(t);
                t.Start();

            }
            taskList.ForEach(x => x.Wait());
            Print();
        }
    }
}


Main 메소드에서 스레드에 sleep을 1초 주었습니다.

처음엔 4개의 thread에서 Task의 작업을 수행하게 됩니다. 그런데 sleep(=부하)가 일어나다 보니 닷넷 프레임워크에선 수행속도가 늦어짐을 감지하고 ThreadPool에서 Thread를 더 추가해 줍니다. 위의 결과는 아래와 같습니다. 최종적인 결과로는 사용한 Thread의 개수는 9개 입니다.



아마도 Thread.Sleep 메소드가 소스에서 제거가 된다면 Thread는 4개만 돌아갈 것입니다. Task를 수행하는데 delay가 없기 때문이지요.


여기서 전 좀 더 많은 스레드를 생성해도 성능상 문제가 없을 텐데라는 생각이 들면서, 좀 더 쉽고, 좀 더 간단하게(직접 ThreadPool을 구현하지 않아도 되는!)Thread의 수를 늘릴 방법을 찾았습니다.


처음에 어느 정도의 스레드의 수를 할당해 놓으면 Task가 delay되는 것을 감지하고 thread를 할당해주는 작업이 생기지 않을 것이기 때문입니다.


그럼 최소 스레드의 개수를 늘려 보겠습니다.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication26
{
    class Program
    {
        static List<int> threadIdList = new List<int>();
        static object lockList = new object();

        static void AddBag(int threadId)
        {
            lock (lockList)
            {
                if (!threadIdList.Contains(threadId))
                    threadIdList.Add(threadId);
            }
        }

        static void Print()
        {
            Console.WriteLine("Thread Count : {0}, Thread IDs : {1}", threadIdList.Count, string.Join(",", threadIdList));
        }

        static void Main(string[] args)
        {
            ThreadPool.SetMinThreads(50, 100);
            List<Task> taskList = new List<Task>();
            for (int i = 0; i < 100; i++)
            {
                Task t = new Task(() =>
                {
                    AddBag(Thread.CurrentThread.ManagedThreadId);
                    Thread.Sleep(1000);
                });

                taskList.Add(t);
                t.Start();

            }
            taskList.ForEach(x => x.Wait());
            Print();
        }
    }
}


메인 메소드에 ThreadPool.SetMinThreads를 이용하여 최소 워커 스레드를 50개로 설정하였습니다.

결과는 아래와 같습니다.



Thread Count가 50개가 되었습니다.

ThreadPool 설정이 Task가 작동하는데 영향이 있다는 것을 확인하였습니다.

ThreadPool 의 개수는 적절히 조절을 해야 합니다. 너무 많은 수는 어플리케이션의 성능을 떨어뜨리니 주의 하는 것이 좋습니다.


아! 마지막으로 중요한 것!

Task 수행은 위에도 설명하였듯이 ThreadPool에서 해당 Task가 작업을 하게 됩니다. 그런데 만약 여기에서 Sleep을 걸게 되면 안됩니다. 왜냐면 전자 Task가 수행되는 Thread는 후자 Task가 대기 할 수도 있기 때문입니다. 후자 Task는 전자 Task가 Sleep을 하고 있기 때문에 기다리다가 다른 Thread로 갈아탑니다. 이렇게 되면 보이지도 않게 퍼포먼스가 떨어지게 됩니다.


즉, 이말은 Task를 수행한다는 것은 최악의 상황일 때 즉시 실행된다는 것을 보장하지 못한다는 것입니다. 정말 성능적으로 중요한 백그라운드 작업이라면 Task를 사용하지 말고 Thread를 생성해서 사용하여야 합니다.


끝~



In to the C# 2014. 1. 14. 11:58

Google Protocol Buffer 사용해보기 with C# - 세번째

Protocol Buffers는 C#만을 위한 라이브러리는 아닙니다.

Protocol Buffers - Developer guide를 보면 C++, Java, Python에 대해서만 설명을 해줍니다. C#은 third party로 분류 되어 있구요.


사실 third-party로 분류된 언어는 많습니다. 

https://code.google.com/p/protobuf/wiki/ThirdPartyAddOns


위 주소로 방문하시면 아시겠지만 Action Script, Go, D, 얼랭, 루아, 펄, 루비, 스칼라, PHP...헉헉..

많네요.


하지만 본인의 주력 언어는 C#이니까!


제가 세번째 글을 포스트 하는 이유는 C#으로 proto 파일을 만들어 내는 방법을 소개해 드리려 합니다.

C#으로 클래스 구현해놓고 타언어 지원을 위해 proto파일을 직접 만들려고 하면 오류도 생길 수 있고, 귀찮고, 위험하잖아요.


소스는 아래와 같습니다.


class 선언한 곳은 기존 예제와 다를 바 없으니 패스 하시고..

가장 아래 메소드 ProtoFileWrite를 보시겠습니다.


실행파일 폴더에서 proto 폴더를 만든 후

파일 저장 Path를 만들고..

Line 62번째!!

GetProto 일반화 메소드가 있습니다! proto파일을 만들려고 하는 타입을 넘겨주면 프로토 파일을 만들어 줍니다.


proto 파일이 어떻게 생성되었는지 보겠습니다.

위 내용은 Packet.proto입니다.



위 내용은 Test.proto입니다.


어때요? 참쉽죠?ㅋ


이렇게 ProtoBuf-net 에 대해서 알아봤습니다.

사실 ProtoBuf-net에는 많은 기능들이 있는데 전부 알아보지는 못했네요. 아쉽지만 나중에 좋은 꼼수들을 알게 될 경우 포스트 할께요~

'In to the C#' 카테고리의 다른 글

Delegates와 Serialization  (0) 2014.01.17
Dispose in C#  (0) 2014.01.14
Thread Pool과 Task  (0) 2014.01.14
Google Protocol Buffer 사용해보기 with C# - 두번째  (1) 2014.01.14
Google Protocol Buffer 사용해보기 with C# - 첫번째  (0) 2014.01.13
In to the C# 2014. 1. 14. 11:14

Google Protocol Buffer 사용해보기 with C# - 두번째

C#에서 ProtoBuf-net을 사용한다는 것은 굉장히 쉽고, 머리아픈 것들을 한방에 날려버립니다.

다른 언어로 개발된 어플리케이션과 데이터를 주고 받을 때도 문제가 없으며, 제너레이터, proto파일 등을 생각할 필요도 없구요. 다만 약간의 단점도 있습니다. 그것은 패킷에 대한 사이즈를 개발자가 직접 컨트롤 할 수가 없다는 것입니다. (아! 이 것은 ProtoBuf-net의 단점이 아니라 Protocol Buffers의 전반적인 내용입니다!!)


가령 int32형 데이터를 하나 보내는데도 4바이트 이상을 소모할 수가 있다는 것입니다. 하지만, 반대로 size가 줄어들 수도 있습니다.


소스를 보시면 총 4번의 직렬화 결과를 얻었습니다.


Line 19 ~ 23은 SizeTest 인스턴스의 key값이 0일 때, 

Line 25 ~ 29은 SizeTest 인스턴스의 key값이 1일 때,

Line 31 ~ 35은 SizeTest 인스턴스의 key값이 int형 min value일 때,

Line 37 ~ 41은 SizeTest 인스턴스의 key값이 int형 max value일 때 입니다.


결과는 아래와 같습니다.



헐!

대박!


같은 int형의 값이라도 byte 배열의 길이가 달라졌네요;;;

값이 0일 때는 제로배열!!

값이 1일 때는 2Byte

int형 min value 일때는 11Byte 씩이나??

int형 max value 일때는 6Byte 이군요!


사실 이글을 쓰고 있는 저는 왜그러는지 잘 모릅니다; 전 protocol buffers를 개발한 사람이 아니고, 어떻게 구현되었는지 파고 싶지도 않거든요..(파고들어도 모르겠지만 ㅠㅠ)


단지 제가 하고 싶은 얘기는 위와 같은 현상이 일어 날 수 있으니까 특히 값이 0일 경우 길이가 0인 배열이 발생하니까 사용하실때 주의 하라는 점을 말씀드리고 싶었습니다.


잇힝~


그럼 세번째 포스트를 기대하세요~ (아마 세번째 포스트가 올라오지 못하면 이 마지막 라인은 사라질겁니다. 헷;

'In to the C#' 카테고리의 다른 글

Delegates와 Serialization  (0) 2014.01.17
Dispose in C#  (0) 2014.01.14
Thread Pool과 Task  (0) 2014.01.14
Google Protocol Buffer 사용해보기 with C# - 세번째  (0) 2014.01.14
Google Protocol Buffer 사용해보기 with C# - 첫번째  (0) 2014.01.13
In to the C# 2014. 1. 13. 18:36

Google Protocol Buffer 사용해보기 with C# - 첫번째

아래 DeathKnight 님께서 추신을 다신 관계로 Protobuf-net에 대해 올리겠습니다.


Protobuf-net.


이름에서도 풍기듯이 .NET 프레임워크에서 사용가능하며, 버전별 빌드를 제공하고 있습니다.

https://code.google.com/p/protobuf-net/


위의 사이트에서 다운을 받을 수도 있고, VisualStudio NuGet에서도 다운로드 받을 수 있습니다.

솔루션 탐색기에서 참조에 protobuf-net을 추가하고, using ProtoBuf를 추가 합니다.


사용방법은 아래와 같습니다.


Line 8 ~ 19까지는 Packet 클래스입니다. [ProtoContract]는 이 클래스는 ProtoBuf-net 직렬화 대상 클래스를 알리는 Attibute 입니다. [Protomember]는 ProtoBuf-net 직렬화 대상 멤버임을 알려주며, 숫자는 Order Number입니다.


Line 21 ~ 32는 Packet 클래스의 세번째 멤버인 리스트에 사용되는 클래스 입니다. 


Line 38 ~ 45에서 테스트용으로 Packet 인스턴스를 채워 주웠구요.


Line 52 ~ 56은 byte 배열로 직렬화 하였습니다. 인스턴스를 MemoryStream 인스턴스에 담은 후 메모리 스트림에서 byte 배열로 변환 합니다.


Line 59 ~ 60은 byte 배열에서 인스턴스로 역직렬화 하였습니다. 그리고 출력하는 내용입니다. 출력 결과는 아래와 같습니다.



타 언어와는 다르게 .proto 파일을 만들 필요가 없으며, generator를 사용할 필요도 없습니다.

우왕 ㅋ굳ㅋ


두번째 ProtoBuf-net 이야기를 기대하세요~ (대단한건 없겠지만요;;;;)

'In to the C#' 카테고리의 다른 글

Delegates와 Serialization  (0) 2014.01.17
Dispose in C#  (0) 2014.01.14
Thread Pool과 Task  (0) 2014.01.14
Google Protocol Buffer 사용해보기 with C# - 세번째  (0) 2014.01.14
Google Protocol Buffer 사용해보기 with C# - 두번째  (1) 2014.01.14
hello world 2014. 1. 13. 17:16

Google Protocol Buffer 사용해보기

참고 : https://developers.google.com/protocol-buffers/?hl=ko


구조화된 데이터의 직렬화( Serializing )를 도와줌


  • 장점 : 익숙한 포멧으로 사용하기 편하고, 다양한 플랫폼을 지원함
  • 단점 : cpp 의 경우 proto 파일이 변경되면 컴파일 해야하고, 그로인한 .h파일의 변경으로 전체 빌드가 될 가능성이 높음

프로토콜의 정의 ( .proto 파일 )

message Person {
    required string name = 1;
    required int32	id = 2;
    optional string	email = 3;

    enum PhoneType {
	MOBILE = 0;
	HOME = 1;
	WORK = 2;
    } 
    message PhoneNumber { 
        required string number = 1;
        optional PhoneType type = 2 [ default = HOME ];
    }
    repeated PhoneNumber phone = 4
}


Cpp 에서 사용 - 쓰기 


proto 에서 정의한 내용을 class 로 사용할 수 있고, 각 멤버 함수를 사용할 수 있다

멤버함수는 알아서 찾아보시고...

Person person;
person.set_name(“John Doe”);
person.set_id(1234);
person.set_email(“123@redduck.com”)
fstream input(“myfile”, ios::in | ios::binary);
person.SirializeToOstream( );

Cpp 에서 사용 - 읽기 


네트워크에서 읽어온 버퍼를 읽던지, 파일에서 읽던지, outstream 에서 읽던지... 

읽으면 클래스에 값이 채워짐

fstream input(“mysql”, ios::in | ios::binary );
Person person;
person.ParseFromIstream(&input)
cout << “Name: “ << person.name() << endl 
     << “Email : “ << person.email() << endl;


Memory DB 와 사용 예 : 

  1. 프로토 버퍼의 SerializeToString 함수를 사용하여 보내는 패킷자체의 String 을 Key-value DB ( 난 couchbase 사용 ) 에 저장 
    1. key='user_id:record' value='......' 이딴식
  2. 다른 유저의 전적을 확인할때, memory-db에 없으면 oracle 에서 찾아서 데이터를 가공한후 memory-db에 저장
  3. 다른 유저 or 같은 데이터를 요청 할때 memory-db 에서 찾아서 클라이언트에 보내줌
  4. memory-db 에 data를 변경할 필요가 있는 경우 그냥 지워버림 ( 그럼 다시 2번 과정 )
tip : 
  • int (int32, uint32, int64, uin64)의 필드를 repeated 로 선언하는 경우 뒤에 [ packed=true ] 옵션을 를 사용하면 패킷이 더 컴팩트 해진다고 합니다. 무조건 붙이세욧

ps : C#에서는 더럽게 편하다고 상욱이가 그랬음




hello world 2014. 1. 13. 15:17

Release Build에서 변수 깨지지 않고 보는 방법


가끔 릴리즈 빌드에서 디버깅을 해야하는 상황이 있습니다.

대부분의 경우 변수값이 깨져서 나오는데, 컴파일 옵션 하나만 추가하면 디버그 빌드처럼 변수를 확인할 수 있습니다.


Visual C++ 2012 이상에서 가능하고, 정식 지원은 아니랍니다.


* 참고사이트 : http://kblog.popekim.com/2013/11/how-to-debug-optimized-code-in-vc2012.html

* 참고사이트 : http://randomascii.wordpress.com/2013/09/11/debugging-optimized-codenew-in-visual-studio-2012/


* 컴파일옵션


 /d2Zi+


현재 진행중인 프로젝트에 적용해봤습니다. (참고로 VS 2012에서 개발 중입니다.)

* 컴파일옵션 적용



* 옵션 적용 전 Watch창



* 옵션 적용 후 Watch창



같은 라인에서 브레이크포인트를 잡고 테스트했는데, 잘 나오네요.


실행 파일은 건들지 않고(확인해보니 파일 크기도 동일), PDB만 바뀐다고하니, Release 빌드에 컴파일 옵션을 추가해줘도 문제가 없을 것같습니다.


hello world 2014. 1. 13. 13:57

글 작성시 syntax highlighter 적용하기

글쓰기를 할대 소스코드를 이쁘게 쓰기 위해서 html 태그를 사용 할 수 있습니다.

소스코드를 보기 좋게 작성하기 위해서는 html 태그를 키고, 

<pre class="brush: cpp" > .... 소스 ....</pre> 

태그 안에 내용을 작성하면 됨

  1. CPP 의 경우 ( calss='brush: cpp' )
    class HighlighterTest {
     bool test( int val ) { 
       if( val > 0 )
        return true;
       else 
        return false;
     }
    };
    
  2. XML 의 경우 ( calss='brush: xml' )
    
      
        12345
      
    
    


카테고리 없음 2014. 1. 13. 11:28

★경 팀블로그 오픈 축하 축★



축하합니다!!