시스템상에 복수의 실행 가능한 스레드가 있는 경우 스케쥴러는 정해진 짧은 시간 간격으로 나누어서 스레드를 CPU에 순서대로 할당하거나 바꾸면서 실행시킨다. 이 쓰레드를 실행시키는 짧은 시간을 "퀀텀 타임(quantum time)"이라고 부른다. 이 시간은 Windows OS 버전과 시스템의 구성 등에 따라 달라진다. 아래는 퀀텀 시간 할당 방법을 변경하는 설정 화면이다.

 

클라이언트 Windows OS에서는 기본적으로 "프로그램"이 선택되어 있다. 이것을 선택하면 GUI의 응답성 등을 향상시키기 위해서 퀀텀 시간이 짧아지고 또 경우에 따라서는 포어 그라운드의 프로세스가 백그라운드 프로세스보다 퀀텀 시간이 길어지도록(즉 포 그라운드 애플리케이션의 응답성이 향상하도록)제어된다. 이를 "퀀텀 부스터"라고 한다.

 

서버 Windows OS의 디폴트 설정에서는 "백그라운드 서비스"가 선택되어 있다. 이것을 선택하면 퀀텀 타임은 "프로그램"에 비해 6("프로그램"과 비교했을 경우의 상대치)의 고정 길이가 되고 모든 프로세스(스레드)가 안정스럽게 똑같이 동작하게 된다(GUI 조작 등에 따라 백그라운드에서 작동하는 프로그램이 별로 영향을 받지 않도록).




 

 

 

출처: http://www.atmarkit.co.jp/ait/articles/1410/30/news150_2.html

저작자 표시
신고
by 흥배 2014.11.10 08:00

MSDN에 있는 글을 옮겨와 봤습니다^^

 

다른 프로세서에서 사용되고 있는 복수의 동시실행 태스크가 같은 캐시라인에 배치되어 있는 변수에 쓰기를 하면 거짓 공유가 발생한다. 하나의 태스크가 어떤 변수에 쓰기를 하면 양쪽 변수의 캐시라인이 무효화된다. 캐시라인이 무효화 될 때마다 재 로딩이 필요하게 된다. 즉 거짓 공유가 발생하면 애플리케이션의 성능이 저하할 위험이 크다.

 

long count1 = 0L;

long count2 = 0L;

concurrency::parallel_invoke(

   [&count1] {

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

         ++count1;

   },

   [&count2] {

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

         ++count2;

   }

);

long count = count1 + count2;

위의 코드는 태스크에 공유 변수를 사용하지 않기 위해 count1, count2로 변수를 나누어서 사용하고 있다. 그래서 일견 보기에는 서로 독립적으로 실행될 것이라고 생각할 수 있다. 그러나 위에 설명했듯이 count1 count2는 서로 같은 캐시라인에 배치될 확률이 높아서 결과적으로 거짓 공유가 발생한다.

 

이 문제를 해결하기 위해서는 아래처럼 메모리 정렬에 의해 서로 다른 캐시라인에 배치되도록 한다.

__declspec(align(64)) long count1 = 0L;     

__declspec(align(64)) long count2 = 0L;     

concurrency::parallel_invoke(

   [&count1] {

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

         ++count1;

   },

   [&count2] {

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

         ++count2;

   }

);

long count = count1 + count2;

핵심은 __declspec(align(64))으로 메모리 캐시 사이즈가 64바이트 이하인 경우 같은 캐시라인을 사용하지 않도록 해준다.

 

 

__declspec 설명

http://blog.naver.com/riverrun27/40126498899

 

 

저작자 표시
신고
by 흥배 2014.02.03 08:00

struct X

{

    std::mutex m;

    int a;

    std::string b;

};

 

void foo(X& a,X& b)

{

    a.m.lock();

    ....

           a.m.unlock();

          

           b.m.lock();

           ....

           b.m.unlock();

}

 

위의 코드에서

X x1, x2;

인스턴스를 생성 후

스레드 1에서 foo( x1, x2 )를 호출하고 동시에 스레드 2에서 f00( x2, x1 )를 호출하면 데드락 상황에 빠지게 됩니다.

 

위 코드는 작아서 이런 실수를 하지 않겠지만 몇 천, 몇 만 코드에서 여러 프로그래머가 같이 작업을 하다 보면 실수를 할 확률이 아주 높고, 문제가 발생하면 쉽게 찾기도 힘들기도 합니다.

 

이 문제를 방지하기 위해서 std::lock 이라는 함수가 있습니다. std::lock 함수를 사용하면 여러  뮤텍스를 동시에 락을 걸 수 있습니다. 아래와 같이

 

std::lock( a.m, b.m );

 

정의는 아래와 같습니다.

template<class L1, class L2, class... L3>

   void lock(L1&, L2&, L3&...);

  

 

 

보통 좀 더 안전하게 뮤텍스를 사용하기 위해서 보통 unique_lock과 같이 사용하는 편입니다.

 

void foo(X& a,X& b)

{

    std::unique_lock<std::mutex> lock_a(a.m,std::defer_lock);

    std::unique_lock<std::mutex> lock_b(b.m,std::defer_lock);

   

           std::lock(lock_a,lock_b);

 

    // 공유 자원을 사용한다

}

 

 

 

저작자 표시
신고
by 흥배 2013.08.26 08:30

앞 선 글 중 ‘std::thread - 2. Join’에서 join을 호출하지 않은 경우 에러 창이 발생하였습니다.


 

이것은 C++11 thread 사양에 의해서 thread 오브젝트가 파괴될 때 join이나 detach를 호출하지 않은 경우 std::terminate를 호출하여 프로그램을 종료하도록 되어 있기 때문입니다.

 

< 예제. 9 >

#include <thread>

#include <iostream>

 

int main()

{

           int nThreadRunCount = 0;

          

           {

                      std::thread Thread = std::thread( [&] ()

                       {

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

                          {

                                ++nThreadRunCount;

                                                               

                                std::cout << "Thread1 ID : " << Thread.get_id() << std::endl;

                          }

                       } );

           }

 

           getchar();

           return 0;

}

 


<예제.9>를 실행하면 에러가 발생하고 아래의 콜스택을 보면



thread 오브젝트의 소멸자에서 terminate를 호출하는 것을 알 수 있습니다.



이것으로 thread에 대해서 기능 위주로 간단하게 살펴 보았습니다. 앞으로 공부해야 할 것들이 더 있어서 남은 것들을 설명 후 다시 thread 사용에 대해서 깊게 들어 가겠습니다.

 

다음부터는 병렬 프로그래밍에서 절대 빠질 수 없는 동기화 객체에 대해서 설명하겠습니다.

 

 

저작자 표시
신고
by 흥배 2013.05.28 08:00

hardware_concurrency()는 하드웨어 스레드 컨텍스트 수를 알려 주는 함수입니다.

스레드 컨텍스트는 CPU , 코어 수, 하이퍼 스레드와 관계가 있습니다.

 

하드웨어 스레드 컨텍스트 수는 Windows '작업 관리자' '성능' 탭에서 볼 수 있습니다.





제 컴퓨터는 CPU 하나에 코어는 4개이고, 하이퍼 스레딩을 지원하지 않는 CPU라서 CPU 사용 현황에 4개의 그림만 나옵니다. 그래서 저는 hardware_concurrency() 함수를 실행하면 결과는 4가 나옵니다.

 

관례적으로 프로그램에서 멀티 스레드를 만들 때 (하드웨어 스레드 컨텍스트 수 * 2) +1 이라는 공식으로 나온 수 만큼 만들면 좋다고 합니다. 이때 hardware_concurrency() 함수를 사용하면 좋습니다.

그리고 만약 hardware_concurrency()를 사용할 수 없는 경우라면 0 을 반환 합니다.

 

< 예제. 7 >

#include <thread>

#include <iostream>

 

 

int main()

{

           std::cout << "하드웨어 컨텍스트 수 : " << std::thread::hardware_concurrency() << std::endl;

 

           return 0;

}

 

< 실행 결과 >


저작자 표시
신고
by 흥배 2013.05.10 08:00

detach 함수는 thread 오브젝트에 연결된 스레드를 떼어냅니다.

그래서 detach 이후에는 thread 오브젝트로 스레드를 제어할 수 없습니다. 그러나 detach를 호출했다고 해서 관련된 스레드가 종료 되는 것이 아닙니다. thread 오브젝트와 스레드의 연결이 끊어지는 것입니다.

 

 

< 예제. 5 >

#include <thread>

#include <iostream>

 

int main()

{

           int nThreadRunCount = 0;

           std::thread Thread = std::thread( [&] ()

                                {

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

                                          {

                                          ++nThreadRunCount;

                                         

                                          std::cout << "Thread1 ID : " << Thread.get_id() << std::endl;

                                          }

                              } );

 

           while( nThreadRunCount < 3 )

           {

           }

 

           Thread.detach();

 

           //Thread.join();

 

           getchar();

           return 0;

}

 

< 실행 결과 >


 

<예제. 5>의 결과를 보면 detach 이후 스레드 id가 초기화 됨을 알 수 있습니다. 그리고 위 예제에서 join() 메소드를 주석 처리하고 있는데 만약 주석을 풀면 아래와 같은 에러가 발생합니다.



 

이렇게 thread 오브젝트가 스레드를 가지지 않는데 join 함수를 호출하면 에러가 발생하는 것을 막기 위해서는 joinable 함수를 사용합니다. joinable 함수를 호출하여 반환 값이 true인 경우에만  join 함수를 호출하면 위와 같은 에러를 막을 수 있습니다.

 

< 예제. 6 >

#include <thread>

#include <iostream>

#include <mutex>

 

int main()

{

           std::mutex mtx_lock;

 

           int nThreadRunCount = 0;

           std::thread Thread = std::thread( [&] ()

                                {

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

                                {

                          ++nThreadRunCount;

                                                               

                                  mtx_lock.lock();

                                  std::cout << "Thread1 ID : " << Thread.get_id() << std::endl;

                                  mtx_lock.unlock();

                                }

                          } );

 

           while( nThreadRunCount < 3 )

           {

                     mtx_lock.lock();

                     std::cout << "Thread Is Joinable : " << Thread.joinable() << std::endl;

                     mtx_lock.unlock();

           }

 

           Thread.detach();

 

           std::cout << "Thread Is Joinable : " << Thread.joinable() << std::endl;

 

           getchar();

           return 0;

}

 

< 실행 결과 3>

 

 

저작자 표시
신고
by 흥배 2013.05.03 08:00

get_id() 함수는 thread 객체에 연결된 스레드의 식별자 id를 반환합니다. get_id()를 사용하면 멀티스레드에서 공용 리소스에 접근하는 스레드가 어떤 것인지 알 수 있습니다.

 

swap() 함수는 thread 객체간의 스레드를 서로 교환할 때 사용합니다. STL 컨테이너에 있는 swap과 같은 역할을 한다고 보면 됩니다.

 

get_id() swap()이 어떤 것인지는 아래 예제 코드를 보시면 이해가 쉬울 것 같습니다.

 

< 예제. 4 >

#include <thread>

#include <iostream>

#include <mutex>

 

int main()

{

           // 동기화 객체. 다음에 자세히 설명하겠습니다.

std::mutex mtx_lock;

 

           int nThreadRunCount1 = 0;

           std::thread Thread1;

           Thread1 = std::thread( [&] ()

                       {

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

                        {

                                   ++nThreadRunCount1;

                                                                                                                                                                           

                                   mtx_lock.lock();

                                   std::cout << "Thread1 ID : " << Thread1.get_id() << std::endl;

                                   mtx_lock.unlock();

                        }

                     } );

          

           int nThreadRunCount2 = 0;

           std::thread Thread2;

           Thread2 = std::thread( [&] ()

                       {

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

                        {

                                   ++nThreadRunCount2;

                                                                                                 

                                   mtx_lock.lock();

                                   std::cout << "Thread2 ID : " << Thread2.get_id() << std::endl;

                                   mtx_lock.unlock();

                       }

                      } );

          

           // Thread1 Thread2가 스레드 ID를 제대로 할당 받은 후 swap하기 위해서 일시 대기

           while( nThreadRunCount1 < 3 && nThreadRunCount2 < 3 )

           {

           }

 

           mtx_lock.lock();

           std::cout << "swap 1,2" << std::endl;

           mtx_lock.unlock();

 

           Thread1.swap( Thread2 );

          

           Thread1.join();

           Thread2.join();

 

           return 0;

}

 

< 결과 >

 


위의 결과를 보면 각 thread 객체마다 스레드 id를 할당 받은 것을 알 수 있습니다. 그리고 swap을 하면 swap 하기 전에 비해서 Thread1 Thread2의 스레드 id가 서로 바뀐 것을 알 수 있습니다.

 

저작자 표시
신고
by 흥배 2013.04.15 08:00

VC10에서 병렬 프로그래밍 라이브러리인 Concurrency Runtime 이라는 것이 추가되었습니다.

Concurrency RuntimeOS의 스레드 관리 보다 좀 더 지능적으로 관리를 하며, Concurrency Runtime Windows 7에서 새로 추가된 UMS(User Mode Scheduler)를 사용하고 있습니다.

 

Windows7 이상의 OS에서 64비트 프로그래밍을 한다면 UMS를 사용하는 것이 좋습니다. 그런데 UMS를 사용하는 방법에 대해서 잘 나온 것이 없습니다. 그래서 UMS를 알아도 활용할 방법이 애매한데 이 문제를 해결하는 방법은 Concurrency Runtime의 스케쥴러에서 스레드를 만드는 것입니다. 기존의 스레드를 만드는 방법과 거의 비슷해서 손 쉽게 이전 코드에서 수정도 가능합니다.

 

 

사용하는 방법은 MSDN의 예제를 참고하면 됩니다.

http://msdn.microsoft.com/en-us/library/vstudio/ee624185.aspx

 

제가 간단하게 설명하자면....

아래의 헤더 파일을 포함합니다

#include <concrt.h>

 

네임스페이스를 선언합니다.

using namespace Concurrency;

 

스레드에서 호출할 함수를 정의합니다.

void __cdecl MyThreadFunction(LPVOID param);

 

스레드에 넘겨줄 데이터를 정의합니다.

typedef struct MyData {

    int val1;

    int val2;

    event signal;

} MYDATA, *PMYDATA;

 

 

Concurrency Runtime의 스케쥴러에서 스레드를 생성합니다.

CurrentScheduler::ScheduleTask(MyThreadFunction, pData);

 

 

위의 MyData 구조체에서 event signal 이 사용되는 경우는 만약 메인 스레드에서 생성한 워커 스레드가 종료 될 때까지 대기하고 싶을 때 사용하면 좋습니다.

메인 스레드에서 아래와 같이 하면 워커 스레드가 종료될 때까지 대기 합니다.

pData->signal.wait();

 

워커 스레드는 스레드가 종료될 때 아래와 같이 해서 이벤트를 발생하여 메인 스레드의 대기를 풀어줍니다.

pData->signal.set();

 

 

저작자 표시
신고
by 흥배 2013.03.04 08:00

앞선 글의 <예제.1>을 보면 제일 아래에

 

#include <thread>

#include <iostream>

 

int main()

{

std::thread Thread1( [] ()

......

 

getchar();

return 0;

}

 

마지막 줄에서 두 번째에 getchar();를 사용했습니다. 만약 이것을 제거하고 예제 코드를 실행하면 다음과 같은 에러가 발생합니다.



이 에러가 발생하는 이유는 아직 스레드는 실행 중인데 프로그램이 종료 되었기 때문입니다. getchar();를 사용하여 유저의 입력을 받을 때까지 대기하도록 하여 문제를 해결할 수 있지만 이것은 임시 방편입니다. 올바른 방법은 thread 클래스의 join 함수를 사용하여 스레드의 실행이 끝날 때까지 대기하도록 합니다.

<예제.1> join을 사용하도록 수정하면 아래와 같습니다.

 

< 예제. 2 >

#include <thread>

#include <iostream>

 

int main()

{

std::thread Thread1( [] ()

           {

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

              {

                     std::cout << "Thread Num : " << i << std::endl;

              }

           } );

 

 

           std::thread Thread2;

           Threads = std::thread( [] ()

           {

              for( int i = 10; i < 15; ++i )

              {

                 std::cout << "Thread Num : " << i << std::endl;

              }

           } );

 

           std::thread Thread3 = std::thread( [] ( int nParam )

                     {

                       for( int i = 20; i < 25; ++i )

                       {

                         std::cout << "Thread Parameter : " << nParam << std::endl;

                       }

                      }, 4 );

 

          

 

           Thread1.join();

Thread2.join();

Thread3.join();

          

return 0;

}

 

<예제.2>는 제일 아래에서 각 thread 객체의 join을 호출하고 있어서 Thread1, Thread2, Thread3는 생성과 동시에 실행이 되는데 만약 Thread1의 실행이 끝난 후 Thread2가 실행되고 이것이 끝나면 Thread3를 실행하려면 thread 객체를 생성 후 join을 호출하면 됩니다.

 

< 예제. 3 >

#include <thread>

#include <iostream>

 

int main()

{

std::thread Thread1( [] ()

           {

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

              {

                     std::cout << "Thread Num : " << i << std::endl;

              }

           } );

           Thread1.join();

 

 

           std::thread Thread2;

           Threads = std::thread( [] ()

           {

              for( int i = 10; i < 15; ++i )

              {

                 std::cout << "Thread Num : " << i << std::endl;

              }

           } );

           Thread2.join();

 

 

           std::thread Thread3 = std::thread( [] ( int nParam )

                     {

                       for( int i = 20; i < 25; ++i )

                       {

                         std::cout << "Thread Parameter : " << nParam << std::endl;

                       }

                      }, 4 );

Thread3.join();

          

return 0;

}

 

< 결과 >




저작자 표시
신고
by 흥배 2012.11.19 08:30

멀티 코어 시대인 만큼 요즘은 프로그램들을 평가할 때 멀티코어를 얼마나 잘 활용하나를 중요하게 따집니다. 그래서 프로그래밍 언어나 라이브러리에서 병렬 프로그래밍 지원을 하는 경우가 많습니다. C++11에서도 큰 특징 중의 하나가 thread 라이브러리 지원입니다.

 

VC의 경우는 현재 사용 중인 10에서는 MS 독자의 Concurrency Runtime을 지원하였고, 이번 버전에서는 C++ 표준의 thread 라이브러리를 제공합니다(VC 전용으로 GPGPU 프로그래밍을 위해 AMP 라이브러리도 제공합니다).

 

C++11 thread 라이브러리를 사용하면 기존에 복잡하고, 플랫폼 마다 다르게 기술해야 하는 스레드 프로그래밍을 쉽고, 플랫폼 독립적으로 구현할 수 있습니다.

 

앞으로 몇 번에 걸쳐서 C++11 thread를 다룹니다. 내용이 어렵지 않으니 천천히 따라오시면 됩니다. thread 라는 것이 무엇인지는 책이나 인터넷을 통해서 미리 공부하시기 바랍니다. 따로 thread에 대한 이론 설명을 하지 않겠습니다. ^^;

 

 

 

thread 생성하기

 

thread 라이브러리를 사용하기 위해서는 아래의 헤더 파일을 포함해야 합니다.

#include <thread>

 

thread 클래스의 생성자는 아래와 같습니다.

thread() _NOEXCEPT;

 

template<class Fn, class... Args>

   explicit thread(Fn&& F, Args&&... A);

 

thread(thread&& Other) _NOEXCEPT;

 

 

인자 중 Fn은 생성된 스레가 호출할 함수(애플리케이션에서 정의한), A Fn에 넘겨 줄 인수 리스트, Other는 기존의 스레드 오브젝트 입니다.

 

첫 번째 생성자는 thread 오브젝트는 만들지만 실제 스레드(OS에서 만들어진 스레드)와는 연결되지 않습니다.

두 번째 생성자는 실제 스레드와 연결된 오브젝트를 생성합니다. 만약 충분한 리소스가 없어서 스레드를 시작할 수 없다면 system_error 오브젝트에 resource_unavailable_try_again 에러코드로 예외를 발생시킵니다.

세 번째 생성자는 다른 thread 오브젝트를 넘겨 받아서 오브젝트를 생성합니다. 인자가 우측 값 참조 이므로 이후에 Other에 연결된 스레드는 Other과 연결이 끊어집니다.

 

아래의 예제를 통해서 스레드를 어떻게 사용하는지 보겠습니다.

 

< 예제. 1 >

#include <thread>

#include <iostream>

 

int main()

{

           std::thread Thread1( [] ()

                        {

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

                                   {

                                          std::cout << "Thread Num : " << i << std::endl;

                                   }

                      } );

 

 

           std::thread Thread2;

           Thread2 = std::thread( [] ()

                        {

                                    for( int i = 10; i < 15; ++i )

                                   {

                                          std::cout << "Thread Num : " << i << std::endl;

                                   }

                         } );

 

           std::thread Thread3 = std::thread( [] ( int nParam )

                        {

                                 for( int i = 20; i < 25; ++i )

                                 {

                                    std::cout << "Thread Parameter : " << nParam << std::endl;

                                 }

                            }, 4 );

 

          

 

           getchar();

           return 0;

}

 

< 결과 >


위 결과를 보면 스레드가 3개 만들어져서 실행된 것을 알 수 있습니다. 그런데 첫 번째 줄에 출력된 결과가 이상하죠? 이것은 공유 객체를 동기화 하지 않아서 발생한 것입니다. 이것은 뒤에 동기화 객체를 설명할 때 고쳐 보겠습니다^^;




ps: 위의 소스는 완벽한 것이 아닙니다. 아마 실행하면 에러가 발생할 것입니다. 이와 관련된 글은 뒤에 적을 글에서 설명할 예정이라서 이번 글의 코드에서는 일부러 조금 불완전한 코드를 사용했습니다.^^;;;


저작자 표시
신고
by 흥배 2012.11.12 09:00
| 1 2 3 |