Thinking and introspection of a wrong program, on race conditions in multithreaded programming

Code directly 

#include <memory>
#include <iostream>
#include <string.h>
#include <vector>
#include <pthread.h>
using namespace std;

class MutexLock
{
public:
    MutexLock()
    {
        pthread_mutex_init(&_mutex, NULL);
    }

    ~MutexLock()
    {
        pthread_mutex_destroy(&_mutex);
    }

    void lock()
    {
        pthread_mutex_lock(&_mutex);
    }

    void unlock()
    {
        pthread_mutex_unlock(&_mutex);
    }

    pthread_mutex_t * getMutexLockPtr()
    {
        return &_mutex;
    }
private:
    pthread_mutex_t _mutex;
};

//RAII
class MutexLockGuard
{
public:
    MutexLockGuard(MutexLock & mutex)
            : _mutex(mutex)
    {
        printf("lock\n");
        _mutex.lock();
    }

    ~MutexLockGuard()
    {
        printf("unlock\n");
        _mutex.unlock();
    }

private:
    MutexLock & _mutex;
};

//end of namespace wd

class Observable;

class Observer{
public:
    virtual void update() = 0;
};

class Foo : public Observer
{
public:
    virtual void update()
    {
        printf("%ld:Foo::update() %p\n",pthread_self(), this);
    }
};


class Observable{
public:
    void register_(weak_ptr<Observer> x)
    {
        observers.push_back(x);
    }

    void notifyObservers()
    {
        MutexLockGuard guard(mutex_);
        Iterator it = observers.begin();
        while(it != observers.end())
        {
            shared_ptr<Observer> obj(it->lock());

            if(obj)
            {
                obj->update();
            }else{
                it = observers.erase(it);
            }
            it++;
        }

    }

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

Observable subject;
int sum = 10;

void* threadOne(void* arg)
{
        int count = 0;
        shared_ptr<Foo> foo = make_shared<Foo>();
        weak_ptr<Foo> w(foo);
        subject.register_(w);
        subject.notifyObservers();


}

void* threadTwo(void* arg)
{
    int count = 0;
    shared_ptr<Foo> foo = make_shared<Foo>();
    subject.register_(foo);
    subject.notifyObservers();


}

void* threadThree(void* arg)
{
    int count = 0;
    shared_ptr<Foo> foo = make_shared<Foo>();
//    subject.register_(foo);
//    subject.notifyObservers();
    count++;
}

int main()
{

    pthread_t thread1;
    pthread_t thread2;
    pthread_t thread3;
    pthread_create(&thread1, nullptr,threadOne,nullptr);
    pthread_create(&thread2,nullptr,threadTwo,nullptr);
    pthread_create(&thread3,nullptr,threadThree,nullptr);

    pthread_join(thread1,nullptr);
    pthread_join(thread2,nullptr);
    pthread_join(thread3,nullptr);

}

 

In fact, you can think about this code. What is wrong with it?

 

The root cause is that my thread did not do timing control  

threadOne

 

threadTwo

 

threadThree
 

These three functions may be in another threadsubject.notifyObservers运行完成之前就结束了,所以observers.erase(it)的时候这个对象已经被释放掉了,然后再次对他observers.erase可能core dump就是他最好的归宿了,所以在这种情况下即使加了锁也并不是安全的 

 

最后我们用一个例子来说明这个问题

 

This is an incorrect example:

int main()
{


    vector<weak_ptr<Foo>> observers;
    ;
    {
        shared_ptr<Foo> p(new Foo);
        weak_ptr<Foo> f(p);
        observers.push_back(f);

    }

    std::vector<weak_ptr<Foo>>::iterator Iterator = observers.begin();
    while(Iterator != observers.end())
    {
        shared_ptr<Observer> obj(Iterator->lock());

        if(obj)
        {
            obj->update();
        }else{
            Iterator = observers.erase(Iterator);
        }
        Iterator++;
    }

}

 

After going out of scope, weak_ptr<Foo> f was released and appeared

Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)

 

but

int main()
{


    vector<weak_ptr<Foo>> observers;
    ;
    {
        shared_ptr<Foo> p(new Foo);
        weak_ptr<Foo> f(p);
        observers.push_back(f);
        std::vector<weak_ptr<Foo>>::iterator Iterator = observers.begin();
        while(Iterator != observers.end())
        {
            shared_ptr<Observer> obj(Iterator->lock());

            if(obj)
            {
                obj->update();
            }else{
                Iterator = observers.erase(Iterator);
            }
            Iterator++;
        }
    }



}

If this is the case, there will be no problem, everything is fine, because the weak_ptr is not released because it has not gone out of scope

/home/zhanglei/ourc/test/cmake-build-debug/test-cpp
140718134794048:Foo::update() 0x55bee3748e70

Process finished with exit code 0

The reason for the error is that I shouldn’t give iterator++ to Iterator, but coredump still appears. I’m really confused about the reason.

 

Finally I checked the c++ primer and saw the introduction of erase in the book

 

erase deletes the specified element in the iterator p and returns an iterator pointing to the deleted element, if it is the end, it points to the off-the-end iterator

 

So here if we use erase, don’t give iterator ++ anymore

 

Finally we write a thread-safe observer pattern

#include <memory>
#include <iostream>
#include <string.h>
#include <vector>
#include <pthread.h>
using namespace std;

class MutexLock
{
public:
    MutexLock()
    {
        pthread_mutex_init(&_mutex, NULL);
    }

    ~MutexLock()
    {
        pthread_mutex_destroy(&_mutex);
    }

    void lock()
    {
        pthread_mutex_lock(&_mutex);
    }

    void unlock()
    {
        pthread_mutex_unlock(&_mutex);
    }

    pthread_mutex_t * getMutexLockPtr()
    {
        return &_mutex;
    }
private:
    pthread_mutex_t _mutex;
};

//RAII
class MutexLockGuard
{
public:
    MutexLockGuard(MutexLock & mutex)
            : _mutex(mutex)
    {
        _mutex.lock();
    }

    ~MutexLockGuard()
    {
        _mutex.unlock();
    }

private:
    MutexLock & _mutex;
};

//end of namespace wd

class Observable;

class Observer{
public:
    virtual void update() = 0;
};

class Foo : public Observer
{
public:
    virtual void update()
    {
        printf("%ld:Foo::update() %p\n",pthread_self(), this);
    }
};


class Observable{
public:
    void register_(weak_ptr<Observer> x)
    {
        MutexLockGuard guard(mutex_);
        observers.push_back(x);
    }

    void notifyObservers()
    {
        MutexLockGuard guard(mutex_);
        Iterator it = observers.begin();
        while(it != observers.end())
        {
            shared_ptr<Observer> obj(it->lock());
            if(obj)
            {
                obj->update();
                it++;
            }else{
                it = observers.erase(it);
            }
        }

    }

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

Observable subject;
int sum = 10;

void* threadOne(void* arg)
{
    shared_ptr<Foo> foo = make_shared<Foo>();
    weak_ptr<Foo> w(foo);
    subject.register_(w);
    subject.notifyObservers();


}

void* threadTwo(void* arg)
{
    shared_ptr<Foo> foo = make_shared<Foo>();
    subject.register_(foo);
    subject.notifyObservers();


}

void* threadThree(void* arg)
{
    shared_ptr<Foo> foo = make_shared<Foo>();
    subject.register_(foo);
    subject.notifyObservers();
}

int main()
{

    pthread_t thread1;
    pthread_t thread2;
    pthread_t thread3;
    pthread_create(&thread1, nullptr,threadOne,nullptr);
    pthread_create(&thread2,nullptr,threadTwo,nullptr);
    pthread_create(&thread3,nullptr,threadThree,nullptr);

    pthread_join(thread1,nullptr);
    pthread_join(thread2,nullptr);
    pthread_join(thread3,nullptr);
}

 

Note that push_back is not thread-safe, there is no lock, it may be sliced ​​to another thread halfway through register_, and then the vector iterator is incomplete, so it also needs to be locked.

Guess you like

Origin blog.csdn.net/qq_32783703/article/details/105301601