文档菜单
文档首页
/ / /
C++ 驱动

线程和分叉安全性

本页内容

  • 错误的线程示例
  • 可接受的线程示例
  • 理想的线程示例
  • 分叉安全性

您应该始终为每个线程分配一个mongocxx::client.

通常,每个 mongocxx::client 对象以及其所有子对象,包括 mongocxx::client_sessionmongocxx::databasemongocxx::collectionmongocxx::cursor,**应同时由单个线程使用**。即使是从 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();

在上面的示例中,尽管两个数据库分别进行了同步,但它们来自同一个客户端。库内部存在共享状态,现在正在没有同步的情况下进行修改。如果 db2db1 的副本,则会出现相同的问题。

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();

在大多数程序中,为了方便和性能,客户端将是长期存在的。在这个人为的例子中,由于我们对每个客户端所做的工 作很少,因此开销相当大,但通常这是最好的解决方案。

在fork时,不能安全地复制mongocxx::clientmongocxx::pool。因此,任何客户端或池都必须在fork之后创建,而不是在fork之前。

返回

高级配置和安装