スレッドとフォークの安全性
常に各スレッドに独自の mongocxx::client
を付与する必要があります。
一般的に、各mongocxx::client
オブジェクトとその子オブジェクト( mongocxx::client_session
、 mongocxx::database
、 mongocxx::collection
、 mongocxx::cursor
など)は、一度に 1 つのスレッドで使用する必要があります。 これは、 mongocxx::pool
から取得されたクライアントにも当てはまります。
単一のclient
から複数の子オブジェクトを作成し、個別に同期しても、 client
の内部構造が同時に変更されるため安全ではありません。 子オブジェクトをコピーする場合も同様です。
誤ったスレッドの例
mongocxx::instance instance{}; mongocxx::uri uri{}; mongocxx::client c{uri}; auto db1 = c["db1"]; auto db2 = c["db2"]; std::mutex db1_mtx{}; std::mutex db2_mtx{}; auto threadfunc = [](mongocxx::database& db, std::mutex& mtx) { mtx.lock(); db["col"].insert_one({}); mtx.unlock(); }; // BAD! These two databases are individually synchronized, but they are derived from the same // client, so they can only be accessed by one thread at a time std::thread t1([&]() { threadfunc(db1, db1_mtx); threadfunc(db2, db2_mtx); }); std::thread t2([&]() { threadfunc(db2, db2_mtx); threadfunc(db1, db1_mtx); }); t1.join(); t2.join();
上記の例では、2 つのデータベースが個別に同期されていますが、これらは同じクライアントから取得されています。 ライブラリ内に共有状態があり、同期せずに変更されています。 db2
がdb1
のコピーである場合も、同じ問題が発生します。
許容スレッドの例
mongocxx::instance instance{}; mongocxx::uri uri{}; mongocxx::client c1{uri}; mongocxx::client c2{uri}; std::mutex c1_mtx{}; std::mutex c2_mtx{}; auto threadfunc = [](std::string dbname, mongocxx::client& client, std::mutex& mtx) { mtx.lock(); client[dbname]["col"].insert_one({}); mtx.unlock(); }; // These two clients are individually synchronized, so it is safe to share them between // threads. std::thread t1([&]() { threadfunc("db1", c1, c1_mtx); threadfunc("db2", c2, c2_mtx); }); std::thread t2([&]() { threadfunc("db2", c2, c2_mtx); threadfunc("db1", c1, c1_mtx); }); t1.join(); t2.join();
理想的なスレッド化の例
mongocxx::instance instance{}; mongocxx::pool pool{mongocxx::uri{}}; auto threadfunc = [](mongocxx::client& client, std::string dbname) { auto col = client[dbname]["col"].insert_one({}); }; // Great! Using the pool allows the clients to be synchronized while sharing only one // background monitoring thread. std::thread t1 ([&]() { auto c = pool.acquire(); threadfunc(*c, "db1"); threadfunc(*c, "db2"); }); std::thread t2 ([&]() { auto c = pool.acquire(); threadfunc(*c, "db2"); threadfunc(*c, "db1"); }); t1.join(); t2.join();
ほとんどのプログラムでは、便宜上とパフォーマンスのためにクライアントが長時間使用されます。 この変換された例では、各クライアントでの作業がほとんどないため、かなりのオーバーヘッドが存在しますが、通常はこれがベストソリューションです。
フォークの安全性
フォーク時に、 mongocxx::client
もmongocxx::pool
も安全にコピーできません。 このため、クライアントまたはプールは、フォーク前ではなく、フォーク後に作成する必要があります。