Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In C++11, is it wise (or even safe) to use std::unique_lock<std::mutex> as a class member? If so, are there any guidelines?

Is it wise (or even safe) to use std::unique_lock as a class member? If so, are there any guidelines?

My thinking in using std::unique_lock was to ensure that the mutex is unlocked in the case of an exception being thrown.

The following code gives an example of how I'm currently using the unique_lock. I would like to know if I'm going in the wrong direction or not before the project grows too much.

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <unistd.h>


class WorkerClass {
private:
    std::thread workerThread;
    bool workerThreadRunning;
    int workerThreadInterval;

    int sharedResource;

    std::mutex mutex;
    std::unique_lock<std::mutex> workerMutex;

public:
    WorkerClass() {
        workerThreadRunning = false;
        workerThreadInterval = 2;

        sharedResource = 0;

        workerMutex = std::unique_lock<std::mutex>(mutex);

        unlockMutex();
    }


    ~WorkerClass() {
        stopWork();
    }


    void startWork() {
        workerThreadRunning = true;
        workerThread = std::thread(&WorkerClass::workerThreadMethod,
                                   this);
    }


    void stopWork() {
        lockMutex();
        if (workerThreadRunning) {
            workerThreadRunning = false;
            unlockMutex();
            workerThread.join();
        }else {
            unlockMutex();
        }
    }


    void lockMutex() {
        try {
            workerMutex.lock();
        }catch (std::system_error &error) {
            std::cout << "Already locked" << std::endl;
        }
    }


    void unlockMutex() {
        try {
            workerMutex.unlock();
        }catch (std::system_error &error) {
            std::cout << "Already unlocked" << std::endl;
        }
    }

    int getSharedResource() {
        int result;
        lockMutex();
        result = sharedResource;
        unlockMutex();
        return result;
    }


    void workerThreadMethod() {
        bool isRunning = true;

        while (isRunning) {
            lockMutex();
            sharedResource++;
            std::cout << "WorkerThread:  sharedResource = "
                      << sharedResource << std::endl;
            isRunning = workerThreadRunning;
            unlockMutex();

            sleep(workerThreadInterval);
        }
    }
};



int main(int argc, char *argv[]) {
    int sharedResource;
    WorkerClass *worker = new WorkerClass();

    std::cout << "ThisThread: Starting work..." << std::endl;
    worker->startWork();

    for (int i = 0; i < 10; i++) {
        sleep(1);

        sharedResource = worker->getSharedResource();
        std::cout << "ThisThread: sharedResource = "
                  << sharedResource << std::endl;
    }

    worker->stopWork();

    std::cout << "Done..." << std::endl;

    return 0;
}
like image 990
Blackwood Avatar asked Oct 27 '25 04:10

Blackwood


1 Answers

this is actually quite bad. storing a std::unique_lock or std::lock_guard as a member variable misses the point of scoped locking, and locking in general.

the idea is to have shared lock between threads, but each one temporary locks the shared resource the lock protects. the wrapper object makes it return-from-function safe and exception-safe.

you first should think about your shared resource. in the context of "Worker" I'd imagine some task queue. then, that task queue is associated with a some lock. each worker locks that lock with scoped-wrapper for queuing a task or dequeuing it. there is no real reason to keep the lock locked as long as some instance of a worker thread is alive, it should lock it when it needs to.

like image 105
David Haim Avatar answered Oct 28 '25 19:10

David Haim



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!