64비트로 빌드하면 유닛테스트 플랫폼을 x64로 맞추어라고 에러가 나올 수가 있는데 

이런 경우 아래처럼 하면 된다.



저작자 표시
신고
by 흥배 2014.10.15 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

unique_lock는 쉽게 말하면 lock_guard에 기능이 더해진 것이라고 볼 수 있습니다.

lock_guard의 경우 정의와 동시에 락을 걸고, 파괴될 때만 락을 풀 수 있습니다.

 

그에 비해 unique_lock은 정의와 동시에 락을 걸 수도 있고, 또는 뒤에 락을 걸 수도 있습니다. 또 락을 푸는 것도 원하는 대로 조절이 가능하며 try_lock이나 try_lock_for, try_lock_until 등도 지원합니다. 그리고 unique_lock끼리 소유한 뮤텍스 객체를 서로 교환도 할 수 있으며, 뮤텍스 객체를 얻거나 해제할 수도 있습니다.

 

간단한 경우에는 lock_guard가 사용하기 편하지만 뮤텍스를 다양하게 사용하고 싶은 경우에는 unique_lock이 훨씬 더 좋습니다.

 

 

unique_lock 생성

unique_lock 클래스의 생성자는 다양한 방법으로 뮤텍스를 사용할 수 있도록 여러 버전이 준비 되어 있습니다.

 

unique_lock() noexcept;

unique_lock(unique_lock&& Other) noexcept;

explicit unique_lock(mutex_type& Mtx);

unique_lock(mutex_type& Mtx, adopt_lock_t Adopt);

unique_lock(mutex_type& Mtx, defer_lock_t Defer) noexcept;

unique_lock(mutex_type& Mtx, try_to_lock_t Try);

template<class Rep, class Period>

   unique_lock(mutex_type& Mtx,

      const chrono::duration<Rep, Period> Rel_time);

template<class Clock, class Duration>

   unique_lock(mutex_type& Mtx,

      const chrono::time_point<Clock, Duration> Abs_time);

unique_lock(mutex_type& Mtx,

   const xtime *Abs_time) noexcept;

 

unique_lock의 사용 예는 아래와 같습니다.

 

std::mutex m;

....

// 락을 만들고 동시에 뮤텍스의 소유권을 가진다

std::unique_lock lck(m, std::adopt_lock);

 

 

std::mutex m;

....

// 락을 만들지만 뮤텍스의 소유권은 가지지 않는다

std::unique_lock lck(m,std::defer_lock);      

...

if (lck.try_lock()) {

....

}                   

 

 

std::unique_lock<std::timed_mutex>

        lk(m,std::chrono::milliseconds(3));

....

if(lk) {

    process(data);

}                   

 

 

 

unique_lock 멤버 함수

 

- void lock();

- mutex_type *mutex() const _NOEXCEPT;

- explicit operator bool() _NOEXCEPT

- unique_lock& operator=(

    unique_lock&& Other

  ) _NOEXCEPT;

- bool owns_lock() const _NOEXCEPT;

- mutex_type *release() _NOEXCEPT;

- void swap(

    unique_lock& Other

  ) _NOEXCEPT;

- bool try_lock() _NOEXCEPT;

- template<class Rep,

    class Period>

  bool try_lock_for(

     const chrono::duration<Rep,

     Period>& Rel_time

  );

- template<class Clock, class Duration>

    bool try_lock_until(const chrono::time_point<Clock, Duration>& Abs_time);

    bool try_lock_until(const xtime *Abs_time);

- void unlock();

 

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

병렬 프로그래밍을 해보면 싫든 좋든 여러 스레드에서 하나의 객체에 동시에 접근하는 경우가 발생합니다. 앞선 std::thread에 관한 글을 보면 소개한 예제 중 그런 경우를 보실 수 있을 겁니다.

 

< 예제 >

#include <thread>

#include <iostream>

 

int main()

{

           std::thread Threads1( [] ()

              {

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

                        {

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

                        }

              } );

 

 

           std::thread Threads2;

           Threads2 = std::thread( [] ()

              {

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

                        {

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

                        }

              } );

 

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

              {

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

                      {

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

                      }

              }, 4 );

 

           getchar();

           return 0;

}

 

위 예제에서 ‘std::cout’ 각 스레드에 동시에 접근하고 있습니다. 그래서 실행하면 아래와 같은 결
과가 나옵니다.


실행 화면을 보면 출력 결과 중 일부가 뒤죽박죽 되어 있는 것을 알 수 있습니다. 이유는 Thread1 스레드에서 출력스트림 결과를 다 출력하기 전에 Threads2가 출력스트림을 사용하여 발생한 문제입니다. 이런 것을 data-race(데이터 경합) 이라고 합니다.

 

그런데 위 예제를 여러 번 실행해 보면 아래처럼 올바르게 나올 때도 있습니다.



사실 이런 부분이 병렬 프로그래밍의 어려움 중의 하나입니다. 여러 스레드에서 하나의 공유 자원을 사용할 때 타이밍에 의해서 순서대로 사용할 수도 있고 그렇지 못할 수도 있기 때문에 버그 발생이 언제 어떻게 생길지 알기 힘듭니다.

 

병렬 프로그래밍을 할 때는 언제나 공유 자원을 최소한으로 하고, 공유 자원은 꼭 동기화 객체를 사용하여 여러 스레드가 동시에 사용하지 못하도록 해야 합니다.

 

위의 문제는 동기화 객체를 사용하면 해결 할 수 있습니다. 여기서는 앞선 글에서 사용한 적이 있는 뮤텍스(std::mutex)를 사용하겠습니다.

 

그럼 위 예제를 mutex를 사용하여 문제를 해결해 보겠습니다.

 

<예제 1 >

#include <thread>

#include <iostream>

#include <mutex>

 

int main()

{

           std::mutex mtx_lock;

 

           std::thread Threads1( [&] ()

              {

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

                        {

                                          mtx_lock.lock();

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

                                          mtx_lock.unlock();

                        }

              } );

 

 

           std::thread Threads2;

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

              {

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

                        {

                                          mtx_lock.lock();

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

                                          mtx_lock.unlock();

                        }

              } );

 

           std::thread Threads3 = std::thread( [&] ( int nParam )

              {

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

                      {

                                 mtx_lock.lock();

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

                                 mtx_lock.unlock();

                      }

              }, 4 );

 

          

 

           getchar();

           return 0;

}

 

< 실행 결과>



std::mutex를 사용하기 위해서는 아래의 헤더 파일을 추가합니다.

#include <mutex>

 

스레드에서 공유 자원을 사용할 때는 아래 처럼 락을 걸어서 뮤텍스 mtx_lock의 소유권을 얻어서 다른 스레드가 접근하지 못하도록 합니다. 그리고 공유 자원을 다 사용하였다면 락을 해제하여 mtx_lock의 소유권을 버립니다.

mtx_lock.lock();

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

mtx_lock.unlock();

 

락을 건 후(lock) 락을 풀(unlock) 때까지는 다른 스레드는 대기를 하고, 락을 풀면 대기하고 있는 스레드 중 하나가 락을 건 후 공유 자원을 사용합니다.

그러므로 락을 건 후에는 꼭 락을 풀어야 하고(만약 풀지 않으면 데드락 상황에 빠지게 됩니다), 락을 사용하는 부위가 너무 빈번하면(스레드 대기가 늘어나므로) 병렬 프로그래밍의 장점이 많이 약해집니다.

 

 

try_lock

std::mutex lock unlock 이외에 try_lock 이라는 멤버 함수가 있습니다.

bool try_lock();

 

스레드에서 lock을 호출할 때 다른 스레드가 이미 lock을 호출하여 뮤텍스의 소유권을 가진 후 아직 unlock을 호출하지 않았다면 그 자리에서 대기를 합니다. 그러나 try_lock은 대기를 하지 않고 false를 반환합니다(뮤텍스의 소유권을 얻지 못한 경우). 반대로 true를 반환한 경우는 뮤텍스에 대한 소유권을 가지게 되므로 공유자원을 안전하게 사용할 수 있습니다.

 

try_lock을 사용하는 경우는 만약 공유 자원을 다른 스레드에서 사용 중이면 스레드가 대기하면서 그냥 시간을 소비하지 않고 또 다른 작업을 하도록 할 때 사용하면 좋습니다.

 


 

ps : 참고로 std::mutex Windows OS에서는 내부적으로는 크리티컬섹션을 사용합니다.

 

 

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

sleep_for sleep_until 함수는 스레드를 일시 중지 시킬 때 사용하는 것입니다.

이 두 함수의 차이는 이름에서 쉽게 알 수 있는데 sleep_for는 특정 시간 동안 일시 중지 시키고 싶을 때(. 6초 동안 중지) 사용하고, sleep_until는 어떤 시간이 될 때까지(. 1410분까지) 중지 시킬 때 사용합니다.

 

sleep_for sleep_until 함수는 thread의 멤버는 아니고 this_thread 네임 스페이스에 속해 있습니다.

 

< 예제. 8 >

#include <iostream>

#include <chrono>

#include <thread>

 

int main()

{

           std::cout << "sleep_for 테스트 시작" << std::endl;

   

           std::chrono::seconds dura( 3 );

   

 

           std::chrono::system_clock::time_point StartTime = std::chrono::system_clock::now();

 

           std::this_thread::sleep_for( dura );

   

           std::chrono::system_clock::time_point EndTime = std::chrono::system_clock::now();

 

           std::chrono::seconds sec = std::chrono::duration_cast<std::chrono::seconds>(EndTime - StartTime);

          

           std::cout << "경과 시간()" << sec.count() << std::endl;

 

 

 

          

           std::cout << "sleep_until 테스트 시작" << std::endl;

 

           StartTime = std::chrono::system_clock::now();

 

           std::chrono::seconds dura_sec( 5 );

           std::this_thread::sleep_until( StartTime + dura_sec );

          

           EndTime = std::chrono::system_clock::now();

 

           sec = std::chrono::duration_cast<std::chrono::seconds>(EndTime - StartTime);

          

           std::cout << "경과 시간()" << sec.count() << std::endl;

}

 

< 실행 결과 > 


 

yield는 스레드가 자신에게 할당 되어 있는 시간을 포기하고 다른 실행 스레드에게 처리를 양도합니다.

std::this_thread::yield();

저작자 표시
신고
by 흥배 2013.05.20 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

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

VC11의 프로젝트 속성 중에 'SDL 검사' 라는 것이 있습니다. 이 속성을 사용으로 하면 보안상 위험한 함수를 사용하면 컴파일 에러를 발생시킵니다.

 

 

 

보안상 위험한 함수 중 가장 대표적인 것이 아래와 같은 문자열 복사입니다.

strcpy( szSource, TEST_TEXT );


보통은 위 함수를 사용하면 컴파일 중 경고로 다른 버전을 사용하라고 출력하는데 SDL 검사를 활성화 하면 에러로 처리합니다.


 

자꾸 위험한 함수를 사용하는 사람이 있으면 이 기능을 사용하여 막을 수 있을 것 같네요 

저작자 표시
신고
by 흥배 2013.03.11 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

웹서핑을 하다가 알게된 사실로 C++ (학생 시절 공부 포함)프로그래밍한지 15년이 넘었는데 이런 사실을 처음 알았네요.

(그런데 왠지 알고나니 왠지 당연하다라는 생각도 되네요....^^;)

 

함수의 인자 값을 디폴트 값을 줄 수 있는 것은 모두 알고 있을 것입니다.

void TEST_1(int nValue = 5 )

{

std::cout << __FUNCTION__ << ". " << nValue << std::endl;

}

 

그런데 TEST_1 nValue의 디폴트 값을 동적으로 줄 수도 있습니다. 정확하게는 nValue의 값을 상수가 아닌 함수로 정의할 수 있습니다. 바로 아래와 같이

int GetNumber()

{

           return (int)time(NULL);

}

 

void TEST_2(int nValue = GetNumber() )

{

           std::cout << __FUNCTION__ << ". " << nValue << std::endl;

}

 

그리고 한술 더 떠서 복수의 식으로 구성할 수도 있습니다.

int g_nValue = 0;

void TEST_3( int nValue = (

                                          g_nValue = GetNumber(),

                                          g_nValue+1) )

{

           std::cout << __FUNCTION__ << ". " << nValue << std::endl;

}

 

이게 어떻게 쓰일지는 모르겠지만 왠지 어딘가에 요긴하게 사용될 것 같네요^^


#include <iostream>

#include <time.h>


void TEST_1(int nValue = 5 )

{

std::cout << __FUNCTION__ << ". " << nValue << std::endl;

}


int GetNumber()

{

return (int)time(NULL);

}


void TEST_2(int nValue = GetNumber() )

{

std::cout << __FUNCTION__ << ". " << nValue << std::endl;

}


int g_nValue = 0;

void TEST_3( int nValue = ( 

g_nValue = GetNumber(),

g_nValue+1) )

{

std::cout << __FUNCTION__ << ". " << nValue << std::endl;

}


int main()

{

TEST_1();


TEST_2();


_sleep(1000);

TEST_2();


TEST_3();


return 0;

}




출처 : http://d.hatena.ne.jp/kabakiyo/20120911/1347369131



 

 

 

 

저작자 표시
신고
by 흥배 2013.02.25 08:00
| 1 2 3 4 |

티스토리 툴바