我需要为嵌入式设备制作自己的简单线程安全共享指针类。 我按照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
有三个不安全的地方:
通过添加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那样完全传递。有时复制构造函数无法复制,因为复制期间删除了源。在这种情况下,将创建空指针。
答案 0 :(得分:0)
这很难做到,我会认真考虑你是否真的需要支持单个对象的并发读写(boost::shared_ptr
和std::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;
};