C++ multithreading learning 06 using RAII

RAII is a concept proposed by Bjarne Stroustrup , the inventor of C++. The full name of RAII is "Resource Acquisition is Initialization". . That is, using local resources to manage objects, under the guidance of RAII, we should use classes to manage resources and bind resources to the life cycle of objects.

1. Manually implement RAII to manage mutex resources

Why do you need to use RAII to manage:
In addition to the low-level mistakes of forgetting to write the unlock statement, an exception may be thrown after locking and cannot be unlocked normally

Under the guidance of RAII, we should use (XMutex class) to manage resources, and bind the resource (mux) with the life cycle of the object lock: define
an XMutex class object lock in the thread to use the RAII mechanism, and the XMutex class accepts a A parameter of the mutex type, which has a reference mux_ pointing to the mutex, locks the incoming mux by locking mux_ when the lock executes its constructor, lock executes its destructor when the thread exits, and passes the lock to execute its destructor mux_unlock is used to unlock mux, so as to use classes to manage resources and bind resources to the life cycle of objects.

using namespace std;
// RAII
class XMutex
{
    
    
public:
    XMutex(mutex& mux):mux_(mux)
    {
    
    
        cout << "Lock" << endl;
        mux_.lock();
    }
    ~XMutex()
    {
    
    
        cout << "Unlock" << endl;
        mux_.unlock();
    }
private:
    mutex& mux_;
};
static mutex mux;
void TestMutex(int status)
{
    
    
    XMutex lock(mux);
    if (status == 1)
    {
    
    
        cout << "=1" << endl;
        return;
    }
    else
    {
    
    
        cout << "!=1" << endl;
        return;
    }
}
int main(int argc, char* argv[])
{
    
    
    TestMutex(1);
    TestMutex(2);
    
    getchar();
    return 0;
}

If the class member is a reference, use the initialization list in the constructor.
Before starting to execute the function body of the constructor, the initialization must be completed. The only opportunity to initialize a const or reference type data member is in the constructor initialization list , because if a member is not explicitly initialized in the constructor initializer list, the member will perform default initialization before the constructor body. After that, enter the constructor body {}. For members of reference and const types, they are very specific. If default initialization is performed, the content they point to will not be changed, so an initialization list is required.

If you exit by multiple conditions, you need to add an unlock before each exit. If you manage through RAII, you can exit with confidence without worrying about unlocking.

As you can see, unlock can be called even without manual unlocking in the test thread:
insert image description here

Important summary! ! :
In the entry parameters of C++ multithreading learning 02 thread, we also saw a similar strategy of using the automatic destruction of objects to control the life cycle. The difference is that in 02, we passed the member function and this pointer of the object to the thread Constructor to bind the entire thread to the object life cycle, here we bind mux to the object life cycle by using the mutex mux as the parameter of the constructor of the RAII manager class

2. RAII management mutual exclusion resources supported by c++11

C++11 implements a strictly scope-based mutex ownership wrapper lock_guard
std:lock_guard class uses RAII method to manage a lock object, and locks the mutex when the object is constructed at startup, without manually calling the lock method and destroying it When unlocking the mutex, this ensures that the mutex can be unlocked when the lock guard object is destroyed in an abnormal situation, and will not block other threads from obtaining the mutex.

Let's first look at the definition of the wrapper lock_guard:

template <class _Mutex>
class lock_guard {
    
     
// class with destructor that unlocks a mutex
//用解除互斥锁的析构函数类
public:
    using mutex_type = _Mutex;

    explicit lock_guard(_Mutex& _Mtx) : _MyMutex(_Mtx) {
    
     // construct and lock
        _MyMutex.lock();
    }

    lock_guard(_Mutex& _Mtx, adopt_lock_t) : _MyMutex(_Mtx) {
    
     // construct but don't lock
    }

    ~lock_guard() noexcept {
    
    
        _MyMutex.unlock();
    }

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

private:
    _Mutex& _MyMutex;
};

Definition analysis:
01lock_guard is a template class that can accept various types of mutexes, such as general mutexes, timeout mutexes, shared mutexes, etc. 02 Differences between
explicit
type constructors and ordinary type constructors
Ordinary constructors can be called explicitly and implicitly, but explicit constructors can only be called explicitly, not implicitly. lock_guard
lock1(gmutex); explicitly call
lock_guard lock2=lock1; implicitly call


The first constructor of lock_guard receives one parameter , and the second constructor of lock_guard can only be explicitly called to receive two parameters. The second parameter adopt_lock means adopting the lock (taking over the previously locked lock), and then the lock Object to complete the work of raising the lock (unlocking), if the code has not been locked before, an error will be reported:
insert image description here

03 Its copy construction and overloaded = are deleted, that is, lock_guard lock2=lock1 or lock_guard lock2 (lock1) cannot be used, that is,

using namespace std;
// RAII

static mutex gmutex;
void TestLockGuard(int i)
{
    
    
    gmutex.lock();
    {
    
    
        //已经拥有锁,不lock
        lock_guard<mutex> lock(gmutex,adopt_lock);
        //结束释放锁
    }
    {
    
    
        lock_guard<mutex> lock(gmutex);
        cout << "begin thread " << i << endl;
    }
    for (;;)
    {
    
    
        {
    
    
            lock_guard<mutex> lock(gmutex);
            cout << "In " << i << endl;
        }
        this_thread::sleep_for(500ms);
    }
}
int main(int argc, char* argv[])
{
    
    
    for (int i = 0; i < 3; i++)
    {
    
    
        thread th(TestLockGuard, i + 1);
        th.detach();
    }

    getchar();
    return 0;
}

The critical section does not necessarily include the entire function, because there should be no sleep in the locked critical section, the lock is still there during sleep, and other threads cannot enter, so use {} to control the critical section of the lock:

 for (;;)
    {
    
    
        {
    
    
            lock_guard<mutex> lock(gmutex);
            cout << "In " << i << endl;
        }
        this_thread::sleep_for(500ms);
    }

When entering {}, lock gmutex by constructing the lock_guard class object, and unlock gmutex by destructing the lock_guard class object when exiting {}. You can see that thread 123 locks, couts, and unlocks in
insert image description here
sequence

Supongo que te gusta

Origin blog.csdn.net/qq_42567607/article/details/125552032
Recomendado
Clasificación