Eventually I decided on a workaround to give each thread an in-memory copy of the database. The following code snippets show a simple example. The example uses the light weight C++ SQLite wrapper from https://github.com/aminroosta/sqlite_modern_cpp
The runThread function creates a copy of the original source database example.sqlite and puts it in memory with a unique connection string. Subsequently, it performs some read operations on the in-memory database.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | int runThread(int threadNumber) { try { cout << "[" << threadNumber << "]" << "Running" << endl; stringstream ss; // Form the connection string to the in-memory database ss << "file:memory" << threadNumber << "?mode=memory"; string inMemoryDbConnectString = ss.str(); ss.str(""); // Open a database connection to the in-memory database database inMemoryDb(inMemoryDbConnectString); // Open a database connection to the original source database database sourceDb("example.sqlite"); // Copy the source database to memory auto sourceConnection = sourceDb.connection(); auto state = std::unique_ptr<sqlite3_backup, decltype(&sqlite3_backup_finish)>( sqlite3_backup_init(inMemoryDb.connection().get(), "main", sourceConnection.get(), "main"), sqlite3_backup_finish ); if (state) { int rc; // Each iteration of this loop copies 500 database pages from database db to the backup database. do { rc = sqlite3_backup_step(state.get(), 500); //std::cout << "Remaining " << sqlite3_backup_remaining(state.get()) << "/" << sqlite3_backup_pagecount(state.get()) << "\n"; } while (rc == SQLITE_OK || rc == SQLITE_BUSY || rc == SQLITE_LOCKED); // Do some reading from the in-memory database table for (int i = 0; i < 10000; i++) { double seconds = 452220.0 + (double)threadNumber + (double)i; double lower = seconds - 1; double upper = seconds + 1; ss << "SELECT seconds, x, y, z FROM attendance WHERE seconds >=" << lower << " AND seconds <=" << upper << " ORDER BY seconds ASC"; inMemoryDb << ss.str() >> [&]( double seconds, double x, double y, double z) { // do something with the returned records }; ss.str(""); } } } catch (sqlite_exception &e) { cout << "[" << threadNumber << "]" << "Error:" << e.what() << endl; } cout << "[" << threadNumber << "]" << "End" << endl; return 0; } |
Finally, the main function that creates multiple threads and waits for them to complete.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | int main(int argc, char **argv) { // Create threads and put them into an array thread threads[3]; for (int i = 0; i < 3; i++) { threads[i] = thread(runThread, i); } // Wait for the threads to complete for (int i = 0; i < 3; i++) { threads[i].join(); } return 0; } |
An screenshot of the program in execution. Since each thread has a copy of the database, the database is never locked by other threads and I will never encounter the "database is locked" exception.
Note that this workaround will not work if read and write operations are required.
No comments:
Post a Comment