ティム・ハリスは言った:ロックベースのプログラムが正しいスレッドセーフフラグメントを構成しないのはなぜですか?
https://en.wikipedia.org/wiki/Software_transactional_memory#Composable_operations
をおそらく最も基本的な反論[...]は、ロックベース プログラムが構成されていないということです:組み合わせたときに正しいフラグメントが失敗することがあります。 の例では、スレッドセーフの挿入と削除を伴うハッシュテーブルを考えて、 オペレーションを削除してください。テーブル t1から1つのアイテムAを削除し、それをテーブルt2に挿入するとします。中間状態( のどちらのテーブルにも項目が含まれていない)は、他のスレッドから見えてはならない。 ハッシュテーブルの実装者がこの必要性を予期しない限り、 は単にこの要件を満たす方法ではありません。 [...]簡潔に言えば、 の操作は個別に正しい(挿入、削除)ことはできません より大きな正しい操作に構成されています。 - Tim Harris他、 「合成可能メモリトランザクション」、セクション2:背景、ページ2 [6]
この意味は?私は2つのハッシュマップstd::unordered_map
と2つのミューテックスstd::mutex
(各ハッシュマップに1つ)を持っている場合は
その後、私は単純にロックすることができ、その両方:http://ideone.com/6RSNyN
#include <iostream>
#include <string>
#include <mutex>
#include <thread>
#include <chrono>
#include <unordered_map>
std::unordered_map<std::string, std::string> map1 ({{"apple","red"},{"lemon","yellow"}});
std::mutex mtx1;
std::unordered_map<std::string, std::string> map2 ({{"orange","orange"},{"strawberry","red"}});
std::mutex mtx2;
void func() {
std::lock_guard<std::mutex> lock1(mtx1);
std::lock_guard<std::mutex> lock2(mtx2);
std::cout << "map1: ";
for (auto& x: map1) std::cout << " " << x.first << " => " << x.second << ", ";
std::cout << std::endl << "map2: ";
for (auto& x: map2) std::cout << " " << x.first << " => " << x.second << ", ";
std::cout << std::endl << std::endl;
auto it1 = map1.find("apple");
if(it1 != map1.end()) {
auto val = *it1;
map1.erase(it1);
std::this_thread::sleep_for(std::chrono::duration<double, std::milli>(1000));
map2[val.first] = val.second;
}
}
int main()
{
std::thread t1(func);
std::this_thread::sleep_for(std::chrono::duration<double, std::milli>(500));
std::thread t2(func);
t1.join();
t2.join();
return 0;
}
私は自分自身、スレッドセーフなハッシュマップmy_unordered_map
を実装する場合その後、私はそのようなことを実施します:
template<typename key, template val>
class my_unordered_map {
std::recursive_mutex mtx_ptr;
void lock() { mtx_ptr->lock(); }
void unlock() { mtx_ptr->unlock(); }
template<typename mutex_type> friend class std::lock_guard;
public:
// .. all required public methods which lock recursive mutex before do anything
void insert(key k, val v) { std::lock_guard<std::recursive_mutex> lock(mtx); /* do insert ... */ }
// ...
};
そして、このような使用します。
my_unordered_map<std::string, std::string> map1 ({{"apple","red"},{"lemon","yellow"}});
my_unordered_map<std::string, std::string> map2 ({{"orange","orange"},{"strawberry","red"}});
void func() {
std::lock_guard<my_unordered_map> lock1(map1);
std::lock_guard<my_unordered_map> lock2(map2);
// work with map1 and map2
// recursive_mutex allow multiple locks in: lock1(map1) and map1->at(key)
}
を
同様にmap1とmap2の両方に対して、スレッドセーフなコードと完全にシーケンシャルな一貫性が得られます。
しかし、これはどのような場合に言われましたか?
おそらく最も基本的な反論[...]は、ロックベース プログラムが構成されていないということです:組み合わせたときに正しいフラグメントが失敗することがあります。
おそらく、操作がトランザクションで失敗するということです。map2がまだキーkを含んでいない場合のみ、map1からキーkを抽出します。トランザクションは両方のmutexを一緒にロックする必要があるため、マップごとに1つのプライベートミューテックスではできません。 –