pyqt6 QThread, threading.Lock() equivalent?

lst = [1, 2, 3, 4, 5, 6, 7, 8, 9]

class Thread(QRunnable):
    def __init__(self):
        super(Thread, self).__init__()
        self.mutex = QMutex()

    def run(self):
        self.mutex.lock()
        lst.pop(0)
        print(str(lst))
        time.sleep(5)
        self.mutex.unlock()

The code above is an example of what I am trying to acheive, I have a list that is defined outside the class. I want to periodically pop the first element of the list. If I am running 5 threads I only want one thread to mutate the list at single time. Everytime I try this the 5 threads all try to pop the first element and dont wait as I want. When I recreate this in the native python threading library, it works as intended. What am I doing wrong here?

Answer

The problem is that you create a mutex per thread. A mutex lock only protects from threads using the same mutex. Since each thread is using its own private lock, its only protected from itself. Expanding on @eyllanesc answer, I’ve created a single mutex for all threads to use. The mutex should be considered associated with the data it protects.

import sys
import time

from PyQt6.QtCore import QCoreApplication, QMutex, QRunnable, QThreadPool, QTimer

# data with mutex access controls    
mutex = QMutex()
lst = [1, 2, 3, 4, 5, 6, 7, 8, 9]


class Thread(QRunnable):
    def run(self):
        #mutex = QMutex()    <-- don't create a private mutex
        mutex.lock()
        lst.pop(0)
        print(lst, time.time()-start, flush=True)
        time.sleep(5)
        mutex.unlock()

start = time.time()

def main():
    app = QCoreApplication(sys.argv)

    QTimer.singleShot(8 * 1000, QCoreApplication.quit)
    pool = QThreadPool()
    for i in range(5):
        runnable = Thread()
        pool.start(runnable)
    sys.exit(app.exec())


if __name__ == "__main__":
    main()