-
Notifications
You must be signed in to change notification settings - Fork 545
Library Thread Safety
In general each mongocxx::client
object AND all of its child mongocxx::database
and mongocxx::collection
objects should be used by a single thread at a time.
Even if you create multiple collection
or database
objects (from a single client
), and synchronize them individually, that is unsafe, as they will use shared structures internally.
Our recommendation is to always give each thread its own mongocxx::client
.
mongocxx::client c{};
auto db1 = c["db1"];
auto db2 = c["db2"];
std::mutex db1_mtx{};
std::mutex db2_mtx{};
auto threadfunc = [](mongocxx::database& db, std::mutex& mtx) {
std::scoped_lock<std::mutex>(mtx);
db["col"].insert({});
}
// 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([]() { threadfunc(db1, db1_mtx); threadfunc(db2, db2_mtx); }
std::thread([]() { threadfunc(db2, db2_mtx); threadfunc(db1, db1_mtx); }
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.
mongocxx::client c1{};
mongocxx::client c2{}
std::mutex c1_mtx{};
std::mutex c2_mtx{};
auto threadfunc = [](stdx::string_view dbname, mongocxx::client& client, std::mutex& mtx) {
std::scoped_lock<std::mutex>(mtx);
client[dbname]["col"].insert({});
}
// Good! these two clients are individually synchronized, so it is safe to share them between
// threads.
std::thread([]() { threadfunc("db1", c1, c1_mtx); threadfunc("db2", c2, c2_mtx); }
std::thread([]() { threadfunc("db2", c2, c2_mtx); threadfunc("db1", c1, c1_mtx); }
// don't even bother sharing clients. Just give each thread its own,
auto threadfunc = [](stdx::string_view dbname) {
mongocxx::client c{};
client[dbname]["col"].insert({});
}
std::thread([]() { threadfunc("db1"); threadfunc("db2"); }
std::thread([]() { threadfunc("db2"); threadfunc("db1"); }
In most programs, clients will be long lived - so its less of a hassle (and more performant) to make one per-thread. Obviously in this contrived example, there's quite a bit of overhead because we're doing so little work with each client - but in a real program this is the best solution.