Zapytanie do bazy danych z dwóch różnych wątków

0

Cześć, w swojej aplikacji uruchomiłem drugi wątek, który wykonuje zapytanie do bazy danych:

QSqlQuery query(...);
query.exec(...)

Ten drugi wątek, po spełnieniu warunku wysyła sygnał do głównego wątku. Tam również wykonuje się podobne zapytanie do tej samej bazy danych.
W tym momencie program wykonuje niespodziewane działanie. W głównym wątku nowy wątek tworzę mniej więcej tak:

void JupiterWindow::makeThread()
{
    FriendsStatuses *friendsStatuses = new FriendsStatuses();
    QThread *thread = new QThread;
    friendsStatuses->moveToThread(thread);
    QObject::connect(thread, &QThread::started, friendsStatuses, &FriendsStatuses::run);
    QObject::connect(friendsStatuses, &FriendsStatuses::statusChanged, this, &JupiterWindow::updateFriendsList);
    thread->start();
}

Bez odwołania do bazy danych w głównym wątku wszystko jest ok. Czy muszę zastosować tu blokadę coś w rodzaju QMutex? Jakie obiekty muszę utworzyć w głównym wątku, żeby to zadziałało? Na ten moment próbowałem w drugim wątku zrobić blokadę w trakcie wywołania zapytania, ale chyba brakuje mi czegoś w głównym wątku?

// DRUGI WĄTEK
QMutex mutex;
mutex.lock();
// zapytanie do bazy danych
mutex.unlock();

Jakie jest najrozsądniejsze rozwiązanie tego problemu? Natknąłem się też na QThreadPool czy QReadWriteLock. Jak to ugryźć?

3

Z tym że ten mutex musi być wspólny dla obu wątków

3

tutaj przydałby się wzorzec w stylu

#include <iostream>
#include <memory>

// zastąp prawdziwym QMuteksem
struct QMutex {
    bool locked = false;
    void lock() {
        if (locked) {
            std::cerr << "trying to lock locked mutex\n";
            std::abort();
        }
        locked = true;
    }
    void unlock() {
        if (!locked) {
            std::cerr << "trying to unlock unlocked mutex\n";
            std::abort();
        }
        locked = false;
    }
};

template <typename T>
class OwningMutex;

template <typename T>
class OwningMutexGuard {
    OwningMutex<T>& mut;
public:
    OwningMutexGuard(OwningMutex<T>& mut)
      : mut(mut)
    {
        mut.mutex.lock();
    }
    ~OwningMutexGuard() {
        mut.mutex.unlock();
    }
    T& operator->() {
        return mut.value;
    }
};

template <typename T>
class OwningMutex {
    QMutex mutex;
    T value;
public:
    OwningMutex(T value)
      : value(std::move(value))
    {}

    OwningMutex(const OwningMutex&) = delete;
    OwningMutex& operator=(const OwningMutex&) = delete;

    [[nodiscard]] OwningMutexGuard<T> lock() {
        return {*this};
    }
    friend class OwningMutexGuard<T>;
};

struct DbConnection {
    void foo(int i) {
        std::cout << "foo" << i << std::endl;
    }
};

DbConnection* get_db_connection() {
    return new DbConnection;
}

int main () {
    auto conn_guarded = OwningMutex<DbConnection*>(get_db_connection());
    {
        auto conn = conn_guarded.lock();
        conn->foo(1);
    }
    {
        auto conn = conn_guarded.lock();
        conn->foo(2);
    }
    {
        auto conn1 = conn_guarded.lock();
        conn1->foo(3);
        auto conn2 = conn_guarded.lock();
        conn2->foo(4);
    }
}

Przykładowy output:

foo1
foo2
foo3
trying to lock locked mutex
fish: Job 1, './a.out' terminated by signal SIGABRT (Przerwij)

Wtedy jak będziesz pamiętać, żeby współdzielonych zasobów nigdy nie trzymać na "goło", tylko zawsze opakowanych w OwningMutex, to nie ma opcji zapomnieć czegoś zalockować, bo po prostu system typów na to nie pozwoli. A jak chcesz przekazywać taki obiekt między wątkami, to nic prostszego, starczy użyć std::shared_ptr<OwningMutex<DbConnection*>>.

10

A connection can only be used from within the thread that created it.. Także może najpierw upewnij się, że każdy wątek ma swoje własne, nie dzielone połączenie i wtedy sprawdź czy Twoje niespodziewane działanie, nadal występuje. Co znaczy w ogóle, niespodziewane działanie?

(edit)
Wg. dokumentacji, żeby mieć dwa różne połączenia, najpierw dodajesz sobie nazwę poprzez addDatabase którego potem używasz w zawołaniu database. Dla każdegu wątku musisz użyć dwóch różnych nazw połączeń.

6

Zamiast kombinować z muteksami, niech każdy wątek ma swoje połączenie z bazą danych.
Baza danych już zajmie sie synchronizacją (chya, że używasz czegoś co tego nie potrafi).

Próbowanie synchronizowania zewnętrznego API (szcególnie skompliowanego) jest szalonym pomysłem często skazanym na porażkę.
Pamiętaj, że w event loop mogą dziać sie rzyczy związane z tym połączeniem, nad którym nie będziesz miał kontroli, więc mimo wszystko będziesz miał race condition.

1

Dzięki za pomoc. Nowe połączenie w drugim wątku okazało się rozwiązaniem problemu. Nie chciałem tego robić wcześniej, bo uważałem, że to może być ponadmiarowe, ale okazuje się, że raczej tak trzeba. Teraz wszystko działa :)

1 użytkowników online, w tym zalogowanych: 0, gości: 1