C++11 atomic을 이용해서 프로그래밍할 때 어떻게 해야 하는지 맛 보기 위해 간단한 예제를 작성해 봤습니다. multiple thread에서 공유 integer 변수를 loop 한번 당 매번 1씩 경쟁적으로 증가시키는 간단한 프로그램입니다.
일반 integer 변수와 atomic integer 변수로 비교해 보았고, 일반 integer 변수의 경우 역시 예상대로 정확한 결과가 나오지 않았고, atomic integer 변수는 정확한 결과가 나왔습니다.
또, 일반 integer 변수는 상당히 빠르게 수행되지만 atomic integer 변수는 상당히 느리다는 것을 확인할 수 있었습니다.
테스트 프로그램은 다음과 같습니다. C++11 프로그램인 만큼 몇 가지 C++11 스타일을 적용했습니다
#include <iostream>
#include <atomic>
#include <vector>
// C++11 <thread>가 있으나 버그가 있어 사용하지 못하고
// boost::thread 사용
#include <boost/thread.hpp>
using namespace std;
using boost::thread;
enum {
NTHREADS = 8, // Core i7 Desktop이라 8로 설정
NRUN = 10000000,
NLOOP = NRUN * 10
};
// atomic integer type. atomic<int>라고도 할 수 있음.
// atomic하게 증가됨
atomic_int an{0};
int n{0};
void increment_atomic(int id, int nloop) {
cout << __PRETTY_FUNCTION__
<< "(" << id << ") starts" << endl;
while (nloop-- > 0) {
// relaxed memory ordering.
// 단순 카운트인 경우에 충분
an.fetch_add(1, memory_order_relaxed);
// an.fetch_add(1, memory_order_seq_cst);
// or ++an; 일반 integer처럼 prefix ++ 지원
// 이 때 memory ordering은 memory_order_seq_cst
if (nloop % NRUN == 0)
cout << __PRETTY_FUNCTION__
<< "(" << id << ") running..." << endl;
}
cout << __PRETTY_FUNCTION__
<< "(" << id << ") exits" << endl;
}
void increment_int(int id, int nloop) {
cout << __PRETTY_FUNCTION__
<< "(" << id << ") starts" << endl;
while (nloop-- > 0) {
++n;
if (nloop % NRUN == 0)
cout << __PRETTY_FUNCTION__
<< "(" << id << ") running..." << endl;
}
cout << __PRETTY_FUNCTION__
<< "(" << id << ") exits" << endl;
}
void test_func(void (*incr)(int, int), void (*print)()) {
vector<thread> thrs{};
cout << "Creating threads" << endl;
for (int i = 0; i < NTHREADS; ++i) {
// 개선된 push_back()이라 할 수 있는
// emplace_back() 활용
thrs.emplace_back(thread(incr, i, NLOOP));
}
cout << "Waiting for all threads to terminate" << endl;
// lambda 함수 사용
for_each(thrs.begin(), thrs.end(), [](thread& thr) {
thr.join();
});
print();
}
void usage() {
cout << "Usage:" << endl;
cout << "test-atomic atomic|int" << endl;
}
int main(int argc, char* argv[]) {
if (argc == 2) {
string type{argv[1]};
if (type == "atomic") {
// lambda 함수 사용
test_func(increment_atomic, []() {
cout << "atomic int = " << an << endl;
});
}
else if (type == "int") {
// lambda 함수 사용
test_func(increment_int, []() {
cout << "int = " << n << endl;
});
}
else {
usage();
}
}
else {
usage();
}
}
컴파일 및 실행 환경은 다음과 같습니다.
$ uname -a
Linux ubuntu 3.2.0-26-generic #41-Ubuntu SMP Thu Jun 14 17:49:24 UTC 2012 x86_64 x86_64 x86_64 GNU/Linux
$ g++ --version
g++-4.6.real (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
Copyright (C) 2011 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
boost thread와 컴파일할 때는 다음과 같이 library를 명시해 줘야 합니다.
$ g++ --std=c++0x -o test-atomic test_atomic.cpp -lboost_thread-mt
실행한 결과는 다음과 같습니다.
$ ./test-atomic
Usage:
test-atomic atomic|int
$ time ./test-atomic atomic
Creating threads
void increment_atomic(int, int)(void increment_atomic(int, int)(void increment_atomic(int, int)(void increment_atomic(int, int)void increment_atomic(int, int)((24) starts) starts1
) starts
3) starts
0) starts
void increment_atomic(int, int)(6) starts
Waiting for all threads to terminate
void increment_atomic(int, int)(7) starts
void increment_atomic(int, int)(5) starts
void increment_atomic(int, int)(6) running...
void increment_atomic(int, int)(3) running...
void increment_atomic(int, int)(5) running...
void increment_atomic(int, int)(0) running...
void increment_atomic(int, int)(1) running...
void increment_atomic(int, int)(7) running...
void increment_atomic(int, int)(2) running...
void increment_atomic(int, int)(4) running...
void increment_atomic(int, int)(5) running...
...
...
void increment_atomic(int, int)(0) exits
void increment_atomic(int, int)(7) running...
void increment_atomic(int, int)(7) exits
void increment_atomic(int, int)(4) running...
void increment_atomic(int, int)(4) exits
void increment_atomic(int, int)(6) running...
void increment_atomic(int, int)(6) exits
void increment_atomic(int, int)(1) running...
void increment_atomic(int, int)(1) exits
void increment_atomic(int, int)(2) running...
void increment_atomic(int, int)(2) exits
atomic int = 800000000
real 0m14.613s
user 1m40.774s
sys 0m0.032s
$ time ./test-atomic int
Creating threads
void increment_int(int, int)(1) starts
void increment_int(int, int)(2) starts
void increment_int(int, int)(0) starts
void increment_int(int, int)(3) starts
void increment_int(int, int)(4) starts
void increment_int(int, int)(5) starts
Waiting for all threads to terminate
void increment_int(int, int)(6) starts
void increment_int(int, int)(7) starts
void increment_int(int, int)(4) running...
void increment_int(int, int)(1) running...
void increment_int(int, int)(7) running...
void increment_int(int, int)(2) running...
void increment_int(int, int)(4) running...
void increment_int(int, int)(5) running...
void increment_int(int, int)(6) running...
...
...
void increment_int(int, int)(3) running...
void increment_int(int, int)(2) running...
void increment_int(int, int)(2) exits
void increment_int(int, int)(0) running...
void increment_int(int, int)(0) exits
void increment_int(int, int)(6) running...
void increment_int(int, int)(3) running...
void increment_int(int, int)(6) running...
void increment_int(int, int)(3) running...
void increment_int(int, int)(3) exits
void increment_int(int, int)(6) running...
void increment_int(int, int)(6) exits
int = 117287057
real 0m2.518s
user 0m16.345s
sys 0m0.004s