在 C++11 中,使用 std::unique_lock<std::mutex> 作为 class 成员是否明智(甚至安全)?如果是这样,是否有任何指导方针?

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?

使用 std::unique_lock 作为 class 成员是否明智(甚至安全)?如果是这样,有什么指导方针吗?

我使用std::unique_lock的想法是确保在抛出异常的情况下解锁互斥锁。

下面的代码给出了我当前如何使用 unique_lock 的示例。我想知道在项目增长太多之前我是否走错了方向。

#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;
}

这其实很糟糕。将 std::unique_lockstd::lock_guard 存储为成员变量错过了作用域锁定和一般锁定的要点。

想法是在线程之间拥有共享锁,但每个临时锁定锁保护的共享资源。包装器对象使其 return-from-function 安全且异常安全。

您首先应该考虑您的共享资源。在 "Worker" 的上下文中,我会想象一些任务队列。然后,该任务队列与某个锁相关联。每个工作人员使用 scoped-wrapper 锁定该锁以对任务进行排队或出队。只要工作线程的某个实例还活着,就没有真正的理由保持锁锁定,它应该在需要时锁定它。

出于多种原因,这样做并不是一个好主意。首先,您已经 "handling" 使用 try-catch 块:两个线程试图锁定同一个锁会导致异常。如果您想要非阻塞锁定尝试,您应该改用 try_lock

第二个原因是,当std::unique_lock在锁的持续时间范围内进行堆栈分配时,当它被破坏时,它会为你解锁资源。这意味着它是异常安全的,如果 workerThread.join() 抛出您当前的代码,那么锁将保持获取状态。