Thread and Fork Safety
On this page
You should always give each thread its own mongocxx::client
.
In general each mongocxx::client
object AND all of its child objects,
including mongocxx::client_session
, mongocxx::database
, mongocxx::collection
,
and mongocxx::cursor
, should be used by a single thread at a time. This
is true even for clients acquired from a mongocxx::pool
.
Even if you create multiple child objects from a single client
, and
synchronize them individually, that is unsafe as they will concurrently
modify internal structures of the client
. The same is true if you copy a
child object.
Incorrect Threading Example
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();
In the above example, even though the two databases are individually
synchronized, they are derived from the same client. There is shared state
inside the library that is now being modified without synchronization. The
same problem occurs if db2
is a copy of db1
.
Acceptable Threading Example
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();
Ideal Threading Example
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();
In most programs, clients will be long lived for convenience and performance. In this contrived example, there's quite a bit of overhead because we're doing so little work with each client, but typically this is the best solution.
Fork Safety
Neither a mongocxx::client
or a mongocxx::pool
can be safely copied
when forking. Because of this, any client or pool must be created after
forking, not before.