'concurrency'에 해당되는 글 2건

  1. 2010.03.29 WCF 서비스의 동시성(Concurrency) - 2
  2. 2010.03.09 WCF 서비스의 동시성(Concurrency) - 1 (4)
지난 포스팅에 이어서 WCF 서비스의 동시성에 대해 이야기 해보겠습니다.
다른 설명 하지 않고, 지난 포스팅에서 했던 것 처럼 예제를 우선 보고 얘기를 진행해볼까 합니다.

Implementing a Singleton

단 하나의 서비스 인스턴스만이 생성되며, 인스턴스에서 동작하는 스레드 역시, 단 하나만 생성되게 하는 경우에 대해 먼저 살펴보겠습니다.
저번 포스팅에서 사용했던 서비스 클래스의 코드를 다음과 같이 굵은 글씨체 부분만을 수정하여 서비스를 실행해 보시기 바랍니다.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,

            ConcurrencyMode = ConcurrencyMode.Single)]

class ProductService : IProductService

{

    ProductService()

    {

        Console.WriteLine("{0}: !!", DateTime.Now);

    }
          ... 생략 ...
}

이 코드를 실행하면 다음과 같은 결과를 확인할 수 있을 것입니다.

[서버]


[클라이언트]


결과가 여러분이 예상했던 것과 일치하나요?? ㅎ

이 경우 역시 서비스의 인스턴스는 서버 측 결과 화면을 통해 클라이언트의 요청의 수와는 관계없이 하나 만이 생성됨을 알 수 있습니다.
그리고, 클라이언트에서 서비스를 호출하는 방식으로 비동기 방식을 사용했던 것 기억하실겁니다. 결과 그림만을 봐서는 잘 모를 수도 있지만, 이 때문에 클라이언트 측 결과 화면을 보면, 지연시간 없이(각 호출에 대한 결과를 받지 않아도) 서비스를 연속적으로 세 번 호출함을 볼 수 있습니다.
하지만, 이 호출에 대한 결과는 각각 5초간의 지연시간을 두고 화면에 출력하는데, 이는 서버측 결과 화면을 보면 그 이유를 알 수 있습니다. 만약, 서비스 인스턴스가 여러 개의 스레드를 만들어 요청을 처리했다면, 클라이언트의 각 요청에 대한 결과를 거의 동시에 받을 수 있겠지만, 이 경우에는 하나의 스레드 만이 동작하기 때문에 각 요청을 한번에 하나씩 처리할 수 있어 이러한 결과를 얻을 수 있는 것이죠. 참고로, 각 요청에 대한 처리는 FIFO(First In First Out)의 순서로 동작합니다.

서비스 인스턴스에서 단 하나의 스레드 만이 동작한다는 것은 서버 측 결과에서 Thread ID 값이 3으로 동일한 것을 봐도 증명이 가능합니다. 가끔, 이 thread id의 값이 각 요청마다 다른 값이 나올 수도 있습니다. 그렇다고 잘못된 결과값은 아닙니다. ConcurrencyMode.Single한번에 하나의 스레드만이 동작한다는 것을 명시하는 거지, 단 하나의 스레드만이 만들어진다라는 의미는 아니거든요~,, 약간 헷갈릴 수도 있는 부분인 것 같으니, 꼭 명심해주세요,, ㅎ

이 경우처럼 싱글 인스턴스, 싱글 스레드는 한번에 하나의 클라이언트 요청만을 처리할 수 있기 때문에 throughput을 감소시킨다는 단점이 있지만, 반면에 시스템 자원(resource)에 동시 접근 같은 문제가 일어나지 않아서, 이에 대한 추가적인 관리가 필요하지 않다는 장점도 있습니다.

Session-Level Instances

이번에는 세션 모드가 적용되었을 때, 서비스의 인스턴스가 어떻게 생성되는지 한번 살펴보도록 하겠습니다.

우선, 서비스에서 세션을 지원하도록 하기 위해 서비스 계약의 특성값을 다음과 같이 수정합니다.

[ServiceContract(Namespace = "http://RuAAService.co.kr/",

                     SessionMode = SessionMode.Required)]

interface IProductService

{

    [OperationContract]

    Product GetProduct();

}


SessionMode에 Required 값을 적용하여 이 서비스가 세션 모드를 지원해준다는 것을 명시해 줍니다.
다음은 ServiceBehavior 특성에서 InstanceContextMode의 값을 PerSession 으로, ConcurrencyMode의 값을 Multiple 로 수정을 해줍니다. 그리고 GetProduct 메서드가 한 인스턴스에서 몇 번 호출이 이루어지는지를 체크하기 위해 n_Calls 라는 이름의 필드를 추가해 주었습니다. lockThis 필드는 n_Calls 필드의 값을 증가시킬 때 다른 스레드에서 동시에 n_Calls의 값을 바꾸지 못하도록 하기 위한 목적으로 선언해주었습니다.


[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession,

            ConcurrencyMode = ConcurrencyMode.Multiple)]

class ProductService : IProductService

{

    object lockThis = new object();

    private int n_Calls = 0;

       
       ... 중간 생략 ...

   
    public
Product GetProduct()

    {

        Console.WriteLine("{0} : GetProduct , Thread Id {1}", 
                            DateTime
.Now, Thread.CurrentThread.ManagedThreadId);

        Thread.Sleep(1000);

 

        Product p = new Product();

        p.ProductId = 1234;

        p.ProductName = "ABC Chocolate";

        p.Price = 1500.0;

        p.Company = "Lotteee";

        p.CreateDate = DateTime.Parse("2010-01-22");

 

        lock (lockThis)

        {

            p.calls = ++n_Calls;

        }

 

        return p;

    }

}


코드를 다 수정하셨으면, 결과를 확인해보도록 하겠습니다.

당연히, 서버 측 콘솔 어플리케이션을 실행 시키신 후에, 클라이언트 어플리케이션을 실행시켜야겠죠,,^^
아,, 이런~ 저와 같은 방법으로 수정을 한 후에 실행 시키면, 아마 예외가 발생하실겁니다.

예외의 이유는 간단합니다. Service Contract에서 세션 모드의 값을 Required로 설정을 해놓았는데, 실제 서비스를 호스팅할 때 세션을 지원하지 않는 BasicHttpBinding을 사용했기 때문입니다. 따라서, BasicHttpBinding을 WSHttpBinding 으로 수정해주시면 이 예외는 발생하지 않을 것입니다.

자~ 이 부분 수정을 다 하셨다면, 다시 실행을 해보도록 하겠습니다.

세션모드의 지원을 확인하기 위해서 클라이언트 어플리케이션을 두 개 연속해서 실행을 시킵니다.

다음은 서버 어플리케이션의 실행 화면입니다.


인스턴스가 두 개 생성된 것을 확인할 수 있네요,, 왜 인스턴스가 두 개 생성되었을까요?? 당연히 클라이언트 어플리케이션이 두 개 실행이 되었기 때문입니다. 세션을 지원하는 서비스이니깐요,,

자~ 다음은 두 클라이언트 어플리케이션의 실행화면입니다.





각 클라이언트는 호출한 횟수가 1~3인 것을 확인할 수 있습니다. 이것은 각 클라이언트 마다의 서비스 인스턴스가 따로 데이터를 유지한다는 것을 의미하는 것이죠. 이해 되셨죠?? ^^

이번 포스팅은 이것으로 마무리 하려 합니다.
이번에도 포스팅이 조금 늦었습니다. 바로 하려고 했는데 이게 마음처럼 쉽지가 않네요. ^^;;

어찌됐든, 다음 포스팅때 뵙도록 하겠습니다. ㅎ
저작자 표시 비영리 변경 금지
신고
Posted by 오태겸(RuAA) 트랙백 0 : 댓글 0

봄이 오고 있네요,, 
날씨가 많이 따뜻해졌고, 해도 부쩍 길어졌음을 느낍니다.
최대한 빠른 시일 내에 포스팅을 하려 했는데, 그동안 무기력증(?)에 빠져있다보니,,
하는거 없이 시간만 보내버렸네요,, ^^;;
봄이 찾아온 만큼 새로운 마음가짐으로 다시 시작해보겠습니다. 아자~!


이번 포스팅의 주제는 WCF의 Behaviors 중에서 서비스의 동시성(Concurrency)을 컨트롤 할 수 있는 Behavior 입니다.

Behavior는 서비스가 동작할 때(그러니깐 런타임 시) 동작에 영향을 끼치는 클래스들로, 서비스 클래스의 특성으로 지정하거나, 환경 설정파일을 통해 지정할 수 있습니다.

Behavior와 관련된 여러 가지 내용 중 이번 포스팅에선 동시성(Concurrency)에 대해서 얘기해보도록 하겠습니다.

동시성이라 함은, 여러 task 들이 동시에 동작하는 것을 말합니다.
동시성은 다들 아시겠지만, 그리고 아주 당연하게도 시스템의 throughput(출력률)에 큰 영향을 끼칩니다. 일정 시간동안 처리할 수 있는 작업의 양이 커지기 때문이죠.

WCF 에서는 동시성을 컨트롤할 수 있는 두 종류의 behavior 가 있습니다. 바로 “InstanceContextMode”“ConcurrencyMode” 입니다.

InstanceContextMode는 생성되는 서비스의 인스턴스를 조절할 수 있는 behavior로 다음과 같은 세 종류의 값으로 설정할 수 있습니다.

  • Single : 이 값은 서비스로 들어오는 모든 요청을 하나의 인스턴스에서 처리하도록 설정합니다.
  • PerCall : 서비스로 들어오는 요청마다 서비스의 인스턴스가 만들어지도록 하기 위한 설정입니다.
  • PerSession : 클라이언트 세션마다의 서비스 인스턴스를 생성하기 위한 설정이며, 만약 세션을 사용하지 않는 채널일 때, 이 값으로 설정이 된다면, PerCall과 같은 방식으로 동작합니다.

그리고, InstanceContextMode의 기본값은 PerSession으로 따로 어떠한 값도 설정되어 있지 않은 경우엔 세션 수에 따라 서비스 인스턴스가 생성됩니다.

WCF에서 동시성을 조절할 수 있는 또 다른 모드인 ConcurrencyMode는 하나의 서비스 인스턴스 내에서 동작하는 스레드를 통한 동시성을 컨트롤하는 behavior입니다.
다음은 ConcurrencyMode에서 설정할 수 있는 값에 대한 설명입니다.

  • Single : 하나의 서비스 인스턴스 내에 오로지 하나의 스레드만이 동작하도록 설정하는 값입니다. 따라서, 이 값으로 설정되어 있는 경우엔 스레딩 문제를 고려하지 않아도 된다는 장점이 있습니다.
  • Reentrant : 이 설정 역시 하나의 서비스 인스턴스에서 하나의 스레드만이 동작하도록 하는 설정값입니다. 하지만, 이 설정값이 Single과 다른 점은 하나의 스레드가 동작하는 도중에 다른 작업이 처리될 수 있다는 것입니다. 이 작업의 처리가 완료되면 이 전의 작업이 계속해서 동작됩니다.
  • Multiple : 하나의 서비스 인스턴스에서 하나 이상의 스레드가 동작할 수 있도록 하는 설정입니다. 이 값으로 설정되어 있는 경우엔 여러 개의 스레드에서 서비스 개체를 변경할 수 있기 때문에 항상 동기화와 상태 일관성을 처리해 주어야 합니다.

ConcurrencyMode와 InstanceContextMode의 값을 적절하게 조합하면, 서비스의 기능에 맞게 동시성과 인스턴스 관리를 할 수 있습니다. 지금 이러한 내용을 글로 써내려가봤자 설명하기도 힘들고, 받아들이기도 힘이 들겁니다. 따라서 이러한 내용은 역시 실제 코드를 작성하고, 결과를 보면서 이해하는게 가장 쉽고 빠른 방법이겠죠 ^^

네~ 이제 InstanceContextMode와 ConcurrencyMode의 값을 적절하게 조합하여 서비스에 적용하는 실습을 해보도록 하겠습니다.

우선, 가장 먼저 세션을 사용하지 않는 환경에서 InstaceContextMode와 ConcurrencyMode의 기본값을 사용한 서비스를 구현해보겠습니다. InstanceContextMode의 기본값은 Single 이며, ConcurrencyMode의 기본값은 PerSession 입니다. 이 기본값은 따로 설정해주지 않아도 적용된다는거 아시죠? ㅎ

다음은 서비스를 구현한 클래스의 코드 입니다.

class ProductService : IProductService

{

    ProductService()

    {

        Console.WriteLine("{0}: 서비스의 새로운 인스턴스 생성!!", DateTime.Now);

    }

 

    public Product GetProduct()

    {

        Console.WriteLine("{0} : GetProduct 호출, Thread Id {1}", DateTime.Now,
Thread.CurrentThread.ManagedThreadId);

        Thread.Sleep(5000);

 

        Product p = new Product();

        p.ProductId = 1234;

        p.ProductName = "ABC Chocolate";

        p.Price = 1500.0;

        p.Company = "Lotteee";

        p.CreateDate = DateTime.Parse("2010-01-22");

 

        return p;

    }

}


저번 포스팅에서 사용했던 서비스의 코드를 살짝 수정 해보았습니다.
서비스 클래스 생성자를 만들어 단순하게 인스턴스가 생성되었다는 메시지를 출력해주는 코드를 추가하였구요, GetProduct 메서드 내에서는 현재 스레드의 ID 값을 출력해주는 코드를 추가하였습니다.

다음은 이 서비스를 호출하는 클라이언트 코드입니다. 코드를 보시면 아시겠지만 클라이언트에서 서비스 메서드를 비동기로 호출하고 있습니다. 혹시 WCF 서비스를 비동기로 호출하는 클라이언트를 만들어보시지 않은 분이 계시면 제가 예전에 올렸던 포스팅을 참고해주시기 바랍니다. (http://ruaa.tistory.com/entry/async-call) 자세한 설명은 없지만 대충은 이해하실 수 있으실겁니다 ^^;;

namespace MySvcAsyncClient

{

    class Program

    {

        static int c = 0;

        static void Main(string[] args)

        {

            ProductServiceClient proxy = new ProductServiceClient();

            for (int i = 0; i < 3; i++)

            {

                Console.WriteLine("{0}: GetProduct 메서드 호출", DateTime.Now);

                proxy.BeginGetProduct(GetProductInfoCallback, proxy);

                Thread.Sleep(100);

                Interlocked.Increment(ref c);

            }

            while (c > 0)

            {

                Thread.Sleep(100);

            }

        }

 

        static void GetProductInfoCallback(IAsyncResult ar)

        {

            ProductInfo productInfo = ((ProductServiceClient)ar.AsyncState)
                                           .EndGetProduct(ar);

            Console.WriteLine("{0} : ProductName : {1}",
                                        
DateTime.Now, productInfo.Name);

            Interlocked.Decrement(ref c);

        }

    }

}


Main 메소드 내에서는 for 문을 사용하여 3번 반복하여 GetProduct 메소드를 비동기로 호출하고 있으며, 각각의 비동기 호출에 의한 작업이 끝이 나면 AsyncCallback 대리자인 GetProductInfoCallback 메소드가 호출되며, 서비스에서 받은 Product 데이터를 화면에 출력해줍니다.

이렇게 코드를 작성하고 나면, 역시 결과가 궁금해 질겁니다. 다음은 이 코드에 대한 결과 화면입니다.

[서버]


[클라이언트]


클라이언트 측 결과 화면을 보면 동시에 서비스의 메소드를 세번 호출하는 것을 확인할 수 있습니다. 그리고 6초 정도의 시간 후에 차례대로 결과값을 가져와서 출력하는 것을 볼 수 있습니다.

서비스 측 결과 화면을 확인해 보면, 각 호출마다 생성자를 통해 새로운 인스턴스를 생성하고, 인스턴스 내에 하나의 스레드를 통해 GetProduct 메소드를 호출하는 것을 확인할 수 있습니다.

여기서 잠깐 의문이 들지도 모르겠습니다. 제가 분명, InstanceContextMode의 기본값은 PerSession 이라고 했는데 왜 서버에선 클라이언트의 호출마다 새로운 인스턴스를 생성한 것일까요?

답은 아주 간단합니다. 서비스를 호스팅할 때 사용했던 binding의 종류가 BasicHttpBinding 이었던 것 기억하시나요? BasicHttpBinding의 경우엔 세션을 사용하지 않기 때문에, 이 경우엔 실제로 InstanceContextMode.PerCall 과 같은 형식으로 동작하게 되는 것입니다.

기본값으로 설정한 경우를 알아봤으니, 이번엔 두 모드의 값을 바꿔서 서비스에 적용해보겠습니다.

인스턴스는 모든 호출에 대해 하나만 생성하도록하고, 스레드의 갯수는 하나 이상으로 만들 수 있게끔 설정한 후에 결과값을 살펴보죠~

앞에서 한번 언급했지만 서비스의 Behavior를 적용하는 방법은 서비스 클래스에 특성으로 설정하는 방법과 config 파일에 설정하는 방법이 있습니다. 여기서는 클래스에 특성으로 설정하는 방법을 사용해보겠습니다.

서비스 클래스의 코드를 다음과 같이 굵은 글씨로 적용된 부분만을 추가해보죠~

class ProductService : IProductService

{
    [ServiceBehavior(InstanceContextMode=InstanceContextMode.Single,

            ConcurrencyMode=ConcurrencyMode.Multiple)]

    ProductService()

    {

        Console.WriteLine("{0}: 서비스의 새로운 인스턴스 생성!!", DateTime.Now);

    }

 

    ... 생략 ...

}


이렇게만 수정한 후에 솔루션을 실행시켜 보면, 클라이언트 측 화면은 변화가 없지만 서버 측 결과 화면은 다음과 같이 변화된 것을 확인하실 수 있으실겁니다.



달라진 점이 무엇인지 보이시죠? ^^

네,, 맞습니다. 인스턴스가 하나만 생성되었다는 점이죠. 아~ 그러고보니 동작한 스레드의 ID 값들이 모두 다른 것도 보이네요. 이 말은 곧, 하나의 인스턴스에 여러 개의 스레드가 생성되었다는 것을 의미하는 것이겠죠. 앞에서 설정했던 InstanceContextMode의 값과 ConcurrencyMode의 값이 어떻게 서비스의 동시성에 적용되었는지 이해가 가실겁니다.

이 외에도 서비스의 동시성에 적용할 수 있는 두 모드의 조합이 더 있지만, 다음 포스팅에서 더 다루도록 하겠습니다. 글도 길어졌고, 아직 담아야 할 내용도 많으니깐요.
이번에는 정말 다음 포스팅때 까지 많이 걸리지 않을 것입니다. 약속드릴께요~ ^^;;

제 포스팅에 항상 댓글 남겨주시고 응원해주시는 분들께 감사 드리며, 또 너무 오랜만에 글을 남겨 죄송한 마음도 듭니다. 제가 잠깐 주춤하긴 했지만, 앞으로는 계속 꾸준한 모습 보여드리려 노력하겠습니다. ^^

감사합니다.
저작자 표시 비영리 변경 금지
신고
Posted by 오태겸(RuAA) 트랙백 0 : 댓글 4

티스토리 툴바