[muduo reading notes] Chapter 1 - smart pointer

The whole content of this article comes from the "Linux multithreaded server programming using muduo network library" written by Mr. Chen Shuo. This is a personal reading note
This paper discusses multithreading safety, and introduces mutex, the function and usage of smart pointer by gradually rewriting the multithreaded version of observer mode

What problems does smart pointer solve?:
As long as the constructor does not leak the this pointer, locking can ensure thread safety;
Otherwise, when thread A destructs an object, B is just accessing the object, and when B judges whether the object is released, A has not been released in time, so B will access A chaotic address content. (dangling pointer)

The introduction of the concept of smart pointer is actually a reference technology. An intermediate layer proxy is added in the middle. This proxy includes a pointer to the real object and a reference counter to help with garbage collection.
– weak_ptr: does not control the object life cycle, but can detect whether the object is still alive. If alive, this can be promoted to effective shared_ptr. If it dies, the promotion fails, and the range is an empty shared_ptr.
– shared_ptr: strong reference, which controls the object lifetime.

MutexLockGuard lock(mutex_);
The deconstruction of lock will be later than that of the returned object, so this shared data is effectively protected.

weak_ptr solves the problem of multi threading in observer mode:
1. Original observer mode:

//Observable class
class Observable{
public:
    void register_(Observer* x);
    void unregister(Observer* x);

    void notifyObservers(){
        for(Observer* x : observers_){
            x->updata(); //***
        }
    }

private:
    vector<Observer*> observers_;
};

//Observer
class Observer{
public:
    Observer(Observable* s){
        s->register_(this);
        subject_ = s;
    }
    
    ~Observer(){
        subject_ ->unregister(this);  //*****
    }
    
    Observable* subject_;
};

Problems:
When thread a executes to line 26 * *, it does not have time to log off the current observer. At the same time, thread B executes to line 9, that is, it just points to the object being destructed by A. in this way, an object being destructed will be updated. What will happen is difficult to predict.

2. Introduce weak_ptr solves Observer race problem
Override Observable class with smart pointer

class Observable{
public:
    void register_(weak_ptr<Observer> x);
    void notifyObservers();

private:
    mutable MutexLock mutex_;
    std::vector<weak_ptr<Observer>> observers_;
    typedef std::vector<weak_ptr<Observer>>::iterator Iterator;
};

void Observable::notifyObservers() {
    MutexLockGuard lock(mutex_); //Lock
    Iterator it = observers_.begin(); //weak_ptr iterator
    while(it != obervers_.end()){
        shared_ptr<Observer> obj(it->lock()); //Try to improve. This step is thread safe
        if(obj){
            //Promotion success
            obj->update();  //***
            ++it;
        }else{
            //Promotion failed, remove weak from container_ ptr
            it-<observers_.erase(it);
        }
    }
}

There is a problem, line 19, * * *. If register or unregister is invoked in Update and mutex can not be reentrant, it may be deadlocked. (Note: reentrant means that a thread has obtained a lock and can obtain the lock again without deadlock.)

3. Completely solve the Oberver mode problem – bypass it
Use Signal/Slots. See book p29 for details

shared_ptr characteristics:
1. Because shared_ There are two members in PTR. One is a counter and the other is a pointer to the underlying object. So shared_ptr itself can not engage in atomic operation, which is thread unsafe. (but the object it points to is thread safe).
I.e. –
A shared_ptr object entities can be read by multiple threads at the same time.
Two shared_ptr object entities can be written by two threads at the same time, and destruct the write operation.
If you want to read and write the same shared from multiple threads_ PTR object, you need to lock it.

2.shared_ptr should be copied carefully, such as in vector and boost::bind

class StockFactory:boost::noncopyable{
public:
    shared_ptr<Stock> get(const string& key);

private:
    mutable MutexLock mutex_;
    std::map<string,shared_ptr<Stock>> stocks_;
};

Because the value in the map is shared_ptr, so stock always has a reference and will never be destroyed.

So we need to use weak_ptr is weakly bound and overrides the get function.
The following code is a weak callback.

class StockFactory:boost::noncopyable{
public:
    shared_ptr<Stock> get(const string& key);

private:
    mutable MutexLock mutex_;
    std::map<string,shared_ptr<Stock>> stocks_;

    void deleteStock(Stock* stock){
        if(stock){
            MutexLockGuard lock(mutex_);
            stocks_erase(stock->key());
        }
        delete stock;
    }
};

shared_ptr<Stock> StockFactory::get(const string &key) {
    shared_ptr<Stock> pStock;
    MutexLockGuard lock(mutex_); //Lock to protect the smart pointer itself?? Actually, I'm not sure, right
    weak_ptr<Stock> &wkStock = stocks_[key];//If the key does not exist, it will be constructed by default
    pStock = wkStock.lock(); //Try to promote. If there is this key, it will be returned directly

    //Otherwise, you need to delete the value in the map
    if(!pStock){
        pStock.reset(new Stock(key),boost::bind(&StockFactory::deleteStock,shared_form_this(),_1));
        wkStock = pStock;
    }

    return pStock;
}

This part of the code is mainly used for get_ PTR performs a promotion detection and reset. Here, it deletes the map when there is no reference.
special:

shared_form_this() -->Point to the current object shared_ptr Object, similar to this

Finally, the author suggests using less cross thread objects

Tags: C++ Multithreading

Posted on Sat, 27 Nov 2021 14:52:08 -0500 by dabigchz