如何在C ++中实现自己的线程安全共享指针?

时间:2014-11-20 12:39:48

标签: c++ multithreading pointers

我需要为嵌入式设备制作自己的简单线程安全共享指针类。 我按照Jeff Alger的书(C ++ for real programmers)中的描述计算了主指针和句柄。这是我的消息来源:

template <class T>
class counting_ptr {
public:
    counting_ptr() : m_pointee(new T), m_counter(0) {}
    counting_ptr(const counting_ptr<T>& sptr) :m_pointee(new T(*(sptr.m_pointee))), m_counter(0)      {}
    ~counting_ptr() {delete m_pointee;}
    counting_ptr<T>& operator=(const counting_ptr<T>& sptr)
    {
        if (this == &sptr) return *this;
        delete m_pointee;
        m_pointee = new T(*(sptr.m_pointee));
        return *this;
    }
    void grab() {m_counter++;}
    void release() 
    {
        if (m_counter > 0) m_counter--;
        if (m_counter <= 0)
            delete this;
    }

    T* operator->() const {return m_pointee;}

private:
    T* m_pointee;
    int m_counter;
};


template <class T>
class shared_ptr {
private:
    counting_ptr<T>* m_pointee;

public:
    shared_ptr() : m_pointee(new counting_ptr<T>()) { m_pointee->grab(); }
    shared_ptr(counting_ptr<T>* a_pointee) : m_pointee(a_ptr) { m_pointee->grab(); }
    shared_ptr(const shared_ptr<T>& a_src) : m_pointee(a_src.m_pointee) {m_pointee->grab(); }
    ~shared_ptr()  { m_pointee->release(); }

    shared_ptr<T>& operator=(const shared_ptr<T>& a_src)
    {
        if (this == &a_src) return *this;
        if (m_pointee == a_src.m_pointee) return *this;
        m_pointee->release();
        m_pointee = a_src.m_pointee;
        m_pointee->grab();
        return *this;
    }
    counting_ptr<T>* operator->() const {return m_pointee;}
};

如果在一个线程中使用它,这可以正常工作。假设我有两个线程:

//thread 1
shared_ptr<T> p = some_global_shared_ptr;
//thread 2
some_global_shared_ptr = another_shared_ptr;

这种情况如果其中一个线程在内存分配/解除分配和计数器更改之间中断,我可能会得到未定义的行为。当然,我可以将shared_ptr :: release()包含在关键部分中,因此可以使指针的删除变得安全。但是我可以用复制构造函数做什么?在m_pointee构造期间,构造函数可能会被另一个将删除此m_pointee的线程中断。 我认为使shared_ptr分配线程安全的唯一方法是将赋值(或创建)包含在关键部分中。但这必须在“用户代码”中完成。换句话说,shared_ptr类的用户必须注意安全性。 是否有可能以某种方式改变这种实现以使shared_ptr类线程安全?

===编辑===

经过一些调查(感谢Jonathan),我意识到我的shared_ptr有三个不安全的地方:

  • Unatomic counter changing
  • Unatomic assignment operator(复制期间可删除源对象)
  • shared_ptr复制构造函数(非常类似于以前的情况)

通过添加crtical部分可以轻松修复前两种情况。但我无法实现如何将临界区添加到复制构造函数中?在构造函数中执行任何其他代码之前创建的a_src.m_pointee的副本,可以在调用grab之前删除。正如乔纳森在评论中所说,解决这个问题非常困难。 我做了这样的测试:

typedef shared_ptr<....> Ptr;
Ptr p1, p2;

//thread 1
while (true)
{
    Ptr p;
    p2 = p;
}

//thread 2
while (!stop)
{
    p1 = p2;
    Ptr P(p2);
}

当然,它崩溃了。但我试图在VS 2013 C ++中使用std :: shared_ptr。它的工作原理! 因此可以为shared_ptr创建线程安全的复制构造函数。但对我而言,这对我来说太难了,我不明白他们是如何做到这一点的。请有人解释我在STL中是如何运作的吗?

===编辑2 ===

对不起,但是std :: shared_ptr的测试错了。它没有像boost :: shared_ptr那样完全传递。有时复制构造函数无法复制,因为复制期间删除了源。在这种情况下,将创建空指针。

3 个答案:

答案 0 :(得分:0)

这很难做到,我会认真考虑你是否真的需要支持单个对象的并发读写(boost::shared_ptrstd::shared_ptr支持除非所有访问都是通过为atomic_xxx()重载并且通常获得锁定的shared_ptr函数完成的。

首先,您需要将shared_ptr<T>::m_pointee更改为atomic<counting_ptr<T>*>,以便您可以原子方式在其中存储新值。 counting_ptr<T>::m_counter需要atomic<int>,因此重新计数更新可以原子方式完成。

您的赋值运算符是一个大问题,您需要至少重新排序操作,以便首先增加引用计数,并避免time of check to time of use错误,如下所示(甚至没有编译,更不用说测试了):

shared_ptr<T>& operator=(const shared_ptr<T>& a_src)
{
    counter_ptr<T>* new_ptr = a_src.m_pointee.load();
    new_ptr->grab();
    counter_ptr<T>* old_ptr = m_pointee.exchange(new_ptr);
    old_ptr->release();
    return *this;
}

这种形式可以安全地防止自我分配(如果两个对象共享同一个指针,它只会增加引用计数然后再次减少它)。当您尝试复制a_src时,它仍然安全。考虑最初a_src.m_pointee->m_counter == 1的情况。当前线程可以调用load()来获取另一个对象的指针,然后第二个线程可以在该指针上调用release(),这将delete它,使{{1}调用未定义的行为,因为它访问已被销毁的对象和已释放的内存。修复需要进行相当大的重新设计,并且可能需要同时对两个单词进行操作的原子操作。

可以做到这一点,但很难,您应该重新考虑是否有必要,或者使用它的代码是否可以避免修改对象,而其他线程正在读取它们,除非用户已锁定互斥锁或其他形式的手动同步。

答案 1 :(得分:0)

经过一些调查后,我可以得出结论,不可能将线程安全理解为线程安全的shared_ptr类:

//thread 1
shared_ptr<T> p = some_global_shared_ptr;
//thread 2
some_global_shared_ptr = another_shared_ptr;

此示例并不保证第一个帖子中的p将指向some_global_shared_ptr的旧值或新值。通常,此示例会导致未定义的行为。使示例安全的唯一方法是将两个操作符包装到关键部分或mutial排除中。

shared_ptr类的复制构造函数导致的主要问题。使用shared_ptr方法中的关键部分可以解决其他问题。

答案 2 :(得分:0)

只需从CmyLock继承您的类,就可以使所有线程安全。 我在所有代码中已经使用了很多年,通常将其与CmyThread类结合使用,后者创建的线程具有非常安全的互斥体。也许我的答案有点晚了,但是以上答案并不是很好的做法。

/** Constructor */
CmyLock::CmyLock()
{
    (void) pthread_mutexattr_init( &m_attr);
    pthread_mutexattr_settype( &m_attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init( &m_mutex, &m_attr);
}

/** Lock the thread for other threads. */
void CmyLock::lock()
{
    pthread_mutex_lock( &m_mutex);
}

/** Unlock the thread for other threads. */
void CmyLock::unlock()
{
    pthread_mutex_unlock( &m_mutex);
}

这里也是线程类。请尝试将CmyLock和CmyThread类复制到您的项目中,并告诉它什么时候起作用!尽管它是为Linux设计的,但Windows和Mac也应该能够运行它。

对于包含文件:

// @brief Class to create a single thread.
class CmyThread : public CmyLock
{
friend void *mythread_interrupt(void *ptr);

public:
    CmyThread();
    virtual ~CmyThread();
    virtual void startWorking() {}
    virtual void stopWorking() {}
    virtual void work();
    virtual void start();
    virtual void stop();
    bool isStopping() { return m_stopThread; }
    bool isRunning() { return m_running && !m_stopThread; }

private:
    virtual void run();

private:
    bool                m_running;      ///< Thread is now running.
    pthread_t           m_thread;       ///< Pointer to thread.
    bool                m_stopThread;   ///< Indicate to stop thread.
};

C ++文件:

/** @brief Interrupt handler.
 *  @param ptr [in] SELF pointer for the instance.
 */
void *mythread_interrupt(void *ptr)
{
    CmyThread *irq =
            static_cast<CmyThread*> (ptr);
    if (irq != NULL)
    {
        irq->run();
    }
    return NULL;
}
/** Constructor new thread. */
CmyThread::CmyThread()
: m_running( false)
, m_thread( 0)
, m_stopThread( false)
{
}

/** Start thread. */
void CmyThread::start()
{
    m_running =true;
    m_stopThread =false;
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    int stack_size =8192*1024;
    pthread_attr_setstacksize(&attr, stack_size);
    pthread_create(&m_thread, &attr, mythread_interrupt, (void*) this);
}

/** Thread function running. */
void CmyThread::run()
{
    startWorking();
    while (m_running && m_stopThread==false)
    {
        work();
    }
    m_running =false;
    stopWorking();
    pthread_exit(0);
}

/** Function to override for a thread. */
virtual void CmyThread::work()
{
    delay(5000);
}

例如,下面是一个用于存储和检索1000个数据的简单示例:

    class a : public CmyLock
    {
        set_safe(int *data)
        {
            lock();
            fileContent =std::make_shared<string>(data); 
            unlock();
        }

        get_safe(char *data)
        {
            lock();
            strcpy( data, fileContent->c_str());
            unlock();
        }
        std::shared_ptr<string> fileContent;
    };