Creative Motive
병렬 프로그래밍 완전 정복 #9 - 병렬 컨테이너와 오브젝트 (2) 본문
데브피아 김경진님 작성 (http://devmachine.blog.me/182050046)
concurrent_unordered_map
concurrency::concurrent_unordered_map 클래스는 std::unordered_map 클래스에 대응하는 병렬 컨테이너입니다. key/value pair 원소를 컨테이너에 추가하여 key를 가지고 value 값을 검색하는 전형적인 dictionary 개념의 컨테이너죠. concurrent_unordered_map 클래스는 여러 스레드 또는 task 에서 컨테이너를 공유하여 동시에 원소를 추가하고 엑세스 하려는 경우에 유용하게 사용될 수 있습니다. 먼저 std::unordered_map 과의 차이점부터 살펴보도록 하죠.
- erase, bucket, bucket_count, bucket_size 메서드의 이름이 unsafe_erase, unsafe_bucket, unsafe_ bucket_count, unsafe_bucket_size 로 각각 변경되었고, 이름에서 예상할 수 있다시피 이 메서드들은 thread-safe 하지 않다.
- 원소의 추가, 엑세스, iterator 엑세스, traversal 작업은 thread-safe 하다. 원소 추가 작업은 iterator를 무효화 시키지 않고 기존에 추가된 원소들의 순서를 변경시키지도 않는다.
- 원소 추가 작업은 equal_range 메서드가 반환한 iterator를 무효화 시키거나 변경하지 않는다.
메서드 | thread_safe | 메서드 | thread_safe | 메서드 | thread_safe |
at | O | count | O | find | O |
key_eq | O | begin | O | empty | O |
get_allocator | O | max_size | O | cbegin | O |
end | O | hash_function | O | operator[] | O |
cend | O | equal_range | O | insert | O |
size | O | clear | X | max_load_factor | X |
rehash | X | load_factor | X | operator= | X |
swap | X | unsafe_xxx | X |
|
|
#include <concurrent_unordered_map.h>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
// concurrent_unordered_map 객체를 생성하고 원소를 병렬로 추가합니다.
concurrent_unordered_map<char, int> map;
parallel_for(0, 10, [&map](int i) {
char key = 'a' + (i%9); // 'a' ~ 'i' 값으로 key 생성
int value = i; // 해당 key에 i 값 할당
map.insert(make_pair(key, value));
});
// concurrent_unordered_map 의 원소 출력
for_each(begin(map), end(map), [](const pair<char, int>& pr) {
wcout << L"[" << pr.first << L", " << pr.second << L"] ";
});
}
몇몇 메서드의 이름 및 동작 방식, thread-safe 여부를 제외하고는 std::unordered_map 과 사용법이 매우 유사하기 때문에 간단하게 대체하여 사용할 수 있습니다. 그리고 위 예제에서는 parallel_for 함수를 이용하여 동시에 원소를 추가하였고 그에 대한 실행 순서는 보장되지 않기 때문에 출력 결과는 달라질 수 있습니다.
concurrent_unordered_multimap
concurrency::concurrent_unordered_multimap 클래스는 같은 key가 여러개의 value를 가질 수 있다는 점을 제외하고는 앞서 설명드린 concurrency::concurrent_unordered_map 클래스와 상당히 유사합니다. 그리고 두 클래스의 추가적인 차이점을 나열하면 다음과 같습니다.
- insert 메서드가 iterator 타입을 반환하는 대신 std::pair<iterator, bool> 타입을 반환한다.
- operator[] 메서드를 제공하지 않는다.
정말 간단하죠? ^^ 위에서 설명드렸던 concurrent_unordered_map 클래스 예제를 그대로 concurrent_unordered_multimap 클래스를 이용하여 작성해 보도록 합시다.
#include <concurrent_unordered_map.h>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
// concurrent_unordered_multimap 객체를 생성하고 원소를 병렬로 추가합니다.
concurrent_unordered_multimap<char, int> map;
parallel_for(0, 10, [&map](int i) {
char key = 'a' + (i%9); // 'a' ~ 'i' 값으로 key 생성
int value = i; // 해당 key에 i 값 할당
map.insert(make_pair(key, value));
});
// concurrent_unordered_multimap 의 원소 출력
for_each(begin(map), end(map), [](const pair<char, int>& pr) {
wcout << L"[" << pr.first << L", " << pr.second << L"] ";
});
}
앞선 예제와는 달리 concurrent_unordered_multimap 클래스의 insert 메서드는 기존에 같은 key 값이 존재하더라도 새로운 value를 추가하기 때문에 출력 결과로 'a' key 에 대한 두 개의 값 [a, 9] [a, 0] 이 출력된 것을 볼 수 있습니다.
concurrent_unordered_set
concurrency::concurrent_unordered_set 클래스는 concurrency::concurrent_unordered_map 클래스와 매우 유사하며 key/value pair 원소 대신 value 원소를 가집니다. 또한 concurrent_unordered_set 클래스는 operator[] 메서드와 at 메서드를 제공하지 않습니다. 이번엔 concurrent_unordered_set 클래스를 이용하여 'a' ~ 'i' 범위의 값을 추가하는 예제를 작성해 보도록 하죠.
#include <concurrent_unordered_set.h>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
// concurrent_unordered_set 객체를 생성하고 원소를 병렬로 추가합니다.
concurrent_unordered_set<char> set;
parallel_for(0, 10000, [&set](int i) {
set.insert('a' + (i%9)); // 'a' ~ 'i' 값 추가
});
// concurrent_unordered_set 의 원소 출력
for_each(begin(set), end(set), [](char c) {
wcout << L"[" << c << L"] ";
});
}
이번에는 출력 결과를 어느 정도 예상하셨으리라 생각되네요. ^^ 이 예제는 앞서 설명한 concurrent_unordered_map 클래스 예제와 거의 유사하기 때문에 자세한 설명은 생략하고 넘어가도록 하겠습니다.
concurrent_unordered_multiset
concurrency::concurrent_unordered_multiset 클래스는 여러개의 중복된 value를 가질 수있다는 점을 제외하고는 concurrency::concurrent_unordered_set 클래스와 상당히 유사합니다. 그리고 insert 메서드가 iterator 타입을 반환하는 대신 std::pair<iterator, bool> 타입을 반환하죠. 이제 마지막 예제를 작성해보도록 하겠습니다.
#include <concurrent_unordered_set.h>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
// concurrent_unordered_multiset 객체를 생성하고 원소를 병렬로 추가합니다.
concurrent_unordered_multiset<char> set;
parallel_for(0, 40, [&set](int i) {
set.insert('a' + (i%9)); // 'a' ~ 'i' 값 추가
});
// concurrent_unordered_multiset 의 원소 출력
for_each(begin(set), end(set), [](char c) {
wcout << L"[" << c << L"] ";
});
}
concurrent_unordered_multiset 클래스는 중복된 value의 추가를 허용하기 때문에 총 40개의 value가 출력된 것을 볼 수 있습니다. 모두 이해되시죠? ^^
자...이렇게 해서 병렬 컨테이너와 오브젝트에 대한 설명도 모두 끝이 났습니다. 아무래도 이번 주제가 이해를 위한 것이 아닌 레퍼런스 개념이다 보니 강좌가 좀 지루하고 딱딱해진 감이 없지 않네요. 저의 코멘트도 딱히 추가할게 없어서 거의 번역에 가까워져 버렸습니다. ^^; 지금까지 강좌 읽으시느라 고생하셨고, 다음 강좌부터는 '병렬 작업의 취소'에 대한 강좌가 이어지니 조금만 더 힘을 내주시기 바랍니다. 감사합니다. ^^
Reference