Segurança de threads e forquilhas
Nesta página
Você deve sempre dar a cada thread seu próprio mongocxx::client
.
Em geral, cada objeto mongocxx::client
E todos os seus objetos filhos, incluindo mongocxx::client_session
, mongocxx::database
, mongocxx::collection
e mongocxx::cursor
, devem ser usados por um único thread de cada vez. Isso vale mesmo para clientes adquiridos de um mongocxx::pool
.
Mesmo que você crie vários objetos filho a partir de um único client
e sincronize-os individualmente, isso não é seguro, pois eles modificarão simultaneamente as estruturas internas do client
. O mesmo vale se você copiar um objeto filho.
Exemplo de threading incorreto
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();
No exemplo acima, embora os dois bancos de dados sejam sincronizados individualmente, eles são derivados do mesmo cliente. Há um estado compartilhado dentro da biblioteca que agora está sendo modificado sem sincronização. O mesmo problema ocorre se db2
for uma cópia de db1
.
Exemplo de threading aceitável
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();
Exemplo de Threading ideal
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();
Na maioria dos programas, os clientes serão de longa duração por conveniência e desempenho. Neste exemplo artificial, há um pouco de sobrecarga porque estamos fazendo muito pouco trabalho com cada cliente, mas normalmente essa é a melhor solução.
Segurança de fork
Nem um mongocxx::client
nem um mongocxx::pool
podem ser copiados com segurança ao bifurcar. Por esse motivo, qualquer cliente ou pool deve ser criado após a bifurcação, não antes.