윈도우에서 스레드 간의 동기화를 위한 다양한 방법을 사용해서 속도를 측정해보도록 합니다. 

테스트 환경 : visual studio 2017, cpu i7-4790(코어4개, 로직스레드 8개)

ElapsedTimeOutput클래스 객체는 https://jungwoong.tistory.com/3 에서 확인 할 수 있습니다.

 

[c++] chrono를 사용한 수행 시간 출력 클래스

c++ 작업을 하다 보면 수행 시간을 측정해보고 싶을 때가 있습니다. 그럴 때 유용하게 사용할 수 있는 클래스를 공유해드립니다. std::chrono를 통해서 구현하였습니다. /** @class : ElapsedTimeOutput @date : 2..

jungwoong.tistory.com

 

스레드간의 동기화 없이 동작할 경우

  테스트를 실행할 내용은 아래와 같습니다.

  - ThreadNonLock 함수는 g_add를 100만 회 더하는 작업을 진행합니다.

  - ThreadNonLock 함수를 실행 시키는 thread를 300개 생성합니다. 

  - 작업이 완료될 때까지 join를 통해서 기다립니다. 

#include <vector>
#include <windows.h>

volatile long g_add = 0;
long g_add = 0;
// 스레드에서 호출 할 함수 
void ThreadNonLock()
{
	for (int i = 0; i < 1000000; i++)
	{
		g_add++;
	}
}


int main()
{
	// 시간 측정을 위한 객체
	ElapsedTimeOutput elapsed("ThreadNonLock");	
    
	vector<thread> threadContainer;
	for (int i = 0; i < 300; i++)
		threadContainer.emplace_back(ThreadNonLock);

	for (auto & t : threadContainer)
	{
		if (t.joinable())
			t.join();
	}
	elapsed.milliSecPrint();
	
	cout << "g_add = " << g_add << endl;
}

 

결과1
결과2
결과3

  총 3회에 걸쳐서 실행을 했고 g_add값은 300000000에 한참 못 미치는 결과가 나왔습니다. 

  아마도 스레드간 동기화 작업을 세팅하지 않아 데이터 레이스가 발생되었기 때문입니다. 

  속도는 평균적으로 200mille sec가 측정되었습니다.

 

Interlocked 함수를 사용한 동기화 

  테스트 방식은 위와 같으며 함수를 더하는 작업을 InterlockedExchangeAdd를 사용해서 값을 수정합니다.

volatile long g_add = 0;
// interLocked 객체 
void ThreadInterLock()
{
	for (int i = 0; i < 1000000; i++)
	{
		InterlockedExchangeAdd(&g_add, 1);
	}
}

int main()
{
	ElapsedTimeOutput elapsed("ThreadInteredLock");	
	vector<thread> threadContainer;
	for (int i = 0; i < 300; i++)
		threadContainer.emplace_back(ThreadInterLock);

	for (auto & t : threadContainer)
	{
		if (t.joinable())
			t.join();
	}
	elapsed.milliSecPrint();
	
	cout << "g_add = " << g_add << endl;

	int input_i;
	cin >> input_i;
	return 0;
}

 

결과1
결과2
결과3

총 3회에 걸쳐서 실행을 했고 InterlockedExchangeAdd 함수에서 스레드 동기화 작업이 진행되어서

NonLock과는 다르게 g_add 값은 정상적인 300000000이 되었습니다. 

수행시간은 평균적으로 대략 5500millie sec로 Non Lock보다 27배나 넘게 걸리는 것으로 확인되었습니다.

std::atomic<long> g_add = 0;
// interLocked 객체 
void ThreadInterLock()
{
	for (int i = 0; i < 1000000; i++)
	{
		g_add.fetch_add(1);
	}
}

int main()
{
	ElapsedTimeOutput elapsed("ThreadInteredLock");
	vector<thread> threadContainer;
	for (int i = 0; i < 300; i++)
		threadContainer.emplace_back(ThreadInterLock);

	for (auto & t : threadContainer)
	{
		if (t.joinable())
			t.join();
	}
	elapsed.milliSecPrint();

	cout << "g_add = " << g_add << endl;

	int input_i;
	cin >> input_i;
	return 0;
}

c++ 표준인 <atomic>으로 구현해 보았는데 비슷한 시간이 걸리는 것으로 확인되었습니다.

크리티컬 섹션을 사용한 동기화

  테스트 방식은 위와 같으며 스레드 동기화시 크리티컬 섹션을 사용합니다. 

volatile long g_add = 0;
CRITICAL_SECTION g_cs;
void ThreadCriticalSection()
{
	for (int i = 0; i < 1000000; i++)
	{
		EnterCriticalSection(&g_cs);
		g_add++;
		LeaveCriticalSection(&g_cs);
	}
}

int main()
{
	InitializeCriticalSection(&g_cs);
	ElapsedTimeOutput elapsed("ThreadCriticalSection");	
	vector<thread> threadContainer;
	for (int i = 0; i < 300; i++)
		threadContainer.emplace_back(ThreadCriticalSection);

	for (auto & t : threadContainer)
	{
		if (t.joinable())
			t.join();
	}
	elapsed.milliSecPrint();
	
	cout << "g_add = " << g_add << endl;

	return 0;
}

결과1
결과2

총 2회에 테스트했고 평균적으로 82000millie sec Non Lock보다 큰 차이가 났습니다. 

 

슬림 리더-라이터 락을 사용한 동기화

  테스트 방식은 위와 같으며 스레드 동기화시 슬림 리더-라이터 락을 사용합니다. 

volatile long g_add = 0;
SRWLOCK g_srwlock;
void ThreadSRWLockWrite()
{
	for (int i = 0; i < 1000000; i++)
	{
		AcquireSRWLockExclusive(&g_srwlock);
		g_add++;
		ReleaseSRWLockExclusive(&g_srwlock);
	}
}

int main()
{
	InitializeSRWLock(&g_srwlock);
	ElapsedTimeOutput elapsed("ThreadSRWLockWrite");	
	vector<thread> threadContainer;
	for (int i = 0; i < 300; i++)
		threadContainer.emplace_back(ThreadSRWLockWrite);

	for (auto & t : threadContainer)
	{
		if (t.joinable())
			t.join();
	}
	elapsed.milliSecPrint();
	
	cout << "g_add = " << g_add << endl;
	return 0;
}

결과1
결과2
결과3

총 3회 테스트를 진행했고 수행시간은 평균 4600millie sec로 다른 동기화 객체에 비해서 빠른 것을 확인하였습니다.

그래도 사용하지 않는 로직에 비해서 20배나 넘게 느린것을 확인 되었습니다.

 

c++의 mutex 락을 이용한 동기화 객체

c++ 11에서 기본지원되는 mutex를 이용한 동기화 객체로 진행해봅니다. 

#include <mutex>
#include <vector>

std::mutex m;
void ThreadMutexLock()
{
	for (int i = 0; i < 1000000; i++)
	{
		std::unique_lock<std::mutex> lk(m);
		g_add++;
	}
}

int main()
{
	ElapsedTimeOutput elapsed("ThreadMutexLock");	
	vector<thread> threadContainer;
	for (int i = 0; i < 300; i++)
		threadContainer.emplace_back(ThreadMutexLock);

	for (auto & t : threadContainer)
	{
		if (t.joinable())
			t.join();
	}
	elapsed.milliSecPrint();
	
	cout << "g_add = " << g_add << endl;

	return 0;
}

 

결과1
결과2
결과3

총 3회 테스트를 진행했고 수행시간은 평균 8100millie sec인 것을 확인하였습니다. 

c++의 shared_mutex 락을 이용한 동기화 객체

#include <shared_mutex>
using namespace std;
volatile long g_add = 0;
std::shared_mutex m;
void ThreadMutexLock()
{
    for (int i = 0; i < 1000000; i++)
    {
        std::unique_lock<std::shared_mutex> lk(m);
        g_add++;
    }
}

int main()
{
    ElapsedTimeOutput elapsed("ThreadSharedMutexLock");
    vector<thread> threadContainer;
    for (int i = 0; i < 300; i++)
        threadContainer.emplace_back(ThreadMutexLock);

    for (auto& t : threadContainer)
    {
        if (t.joinable())
            t.join();
    }
    elapsed.milliSecPrint();

    cout << "g_add = " << g_add << endl;

    return 0;
}

결과1
결과2
결과3

[최종 확인 결과]

락 종 류 시   간
Interlocked함수 5500millie sec
크리티컬 섹션 82000millie sec
슬림 리더-라이터 락 4600millie sec
std::mutex 8100millie sec
std::shared_mutex 5500millie sec

최대한 공유리소스를 사용하지 않는 것이 프로그램에 효율적이며

공유 리소스를 사용한다면 슬림 리더-라이터 락을 사용하는 것이 가장 효율적인것으로 확인 되었습니다.

https://jungwoong.tistory.com/19?category=1067195

 

[Window c++] 슬림 리더-라이터 락 (SWRLock)

SRWLock(Slim Reader-Writer Lock)은 크리티컬 섹션과 마찬가지로 공유자원을 다수의 스레드가 접근할 때 공유자원을 보호할 목적으로 사용합니다. 크리티컬 섹션과의 차이점은 공유 자원을 읽기만 하는 스레드와..

jungwoong.tistory.com

윈도우 뿐만아니라 다른 다양한 OS에서 지원되어야 한다면 std::mutex와 std::shared_mutex 사용하는 것도

나쁘지 않을 듯 합니다.

'c++ > windows' 카테고리의 다른 글

[window c++] 스레드  (0) 2020.02.04
[windows c++] 프로세스  (1) 2020.01.31
[Window c++] 커널 오브젝트  (0) 2020.01.20
[Window c++] window c++ 에서 문자와 문자열  (0) 2020.01.16
[동기화객체] 조건 변수(Condition Variable)  (0) 2019.10.05

+ Recent posts