检测何时将对象传递给 C++ 中的新线程?
Posted
技术标签:
【中文标题】检测何时将对象传递给 C++ 中的新线程?【英文标题】:Detecting when an object is passed to a new thread in C++? 【发布时间】:2009-06-26 15:23:40 【问题描述】:我有一个对象,我想跟踪引用它的线程数。通常,当调用对象上的任何方法时,我可以检查线程本地布尔值以确定当前线程的计数是否已更新。但是,如果用户说使用 boost::bind 将我的对象绑定到 boost::function 并使用它来启动 boost::thread,这对我没有帮助。新线程将引用我的对象,并且在调用它的任何方法之前可能会无限期地持有它,从而导致过时的计数。我可以围绕 boost::thread 编写自己的包装器来处理这个问题,但如果用户 boost::bind 是一个对象 包含 我的对象(我不能专门基于成员类型的存在——至少我不知道有什么方法可以做到这一点)并使用它来启动 boost::thread。
有没有办法做到这一点?我能想到的唯一方法需要用户做太多工作——我提供了一个围绕 boost::thread 的包装器,它在传入的对象上调用一个特殊的钩子方法,前提是它存在,并且用户将这个特殊的钩子方法添加到任何类包含我的对象。
编辑:为了这个问题,我们可以假设我控制了制作新线程的方法。所以我可以包装 boost::thread 例如并期望用户会使用我的包装版本,而不必担心用户同时使用 pthread 等。
Edit2:也可以假设我有一些可用的线程本地存储方式,通过__thread
或boost::thread_specific_ptr
。它不在当前标准中,但希望很快就会出现。
【问题讨论】:
我觉得你的问题很难解决。那是因为引用有点或内存指向另一个内存位置(+或多或少额外的东西)。因为内存在线程之间共享,所以您无法分辨哪个线程拥有引用,因为所有线程都“拥有”内存,所以它们都拥有引用。这并不意味着这是不可能的,而是因为技术基础总是分享东西,所以很难做到。 是的,我希望通过控制线程的创建方式,我可以以某种方式解决这个问题。我已经编辑了我的帖子以明确这种可能性。 【参考方案1】:一般来说,这很难。 “谁提到了我?”的问题。在 C++ 中通常无法解决。可能值得从您尝试解决的特定问题的大局来看,看看是否有更好的方法。
我可以想出一些可以让你成功的方法,但它们都不是你想要的。
您可以为一个对象建立“拥有线程”的概念,以及来自任何其他线程的 REJECT 操作,就像 Qt GUI 元素一样。 (请注意,尝试从所有者以外的线程安全地执行操作实际上不会给您线程安全,因为如果不检查所有者,它可能会与其他线程发生冲突。)这至少会让您的用户失败-快速的行为。
您可以通过让用户可见的对象成为对实现对象本身的轻量级引用来鼓励引用计数[并记录下来!]。但坚定的用户可以解决这个问题。
您可以将这两者结合起来——即您可以为每个引用拥有线程所有权的概念,然后让对象知道谁拥有引用。这可能非常强大,但并不是真正的白痴。
您可以开始限制用户可以和不可以对对象执行的操作,但我认为不值得只覆盖明显的意外错误来源。您是否应该将 operator& 声明为私有,这样人们就无法获取指向您的对象的指针?您是否应该阻止人们动态分配您的对象?这在某种程度上取决于您的用户,但请记住,您无法阻止对对象的引用,因此最终玩打地鼠会让您发疯。
所以,回到我最初的建议:如果可能,重新分析全局。
【讨论】:
【参考方案2】:在每次取消引用之前都会执行 threadid 检查的 pimpl 样式实现的不足我不知道您如何做到这一点:
class MyClass;
class MyClassImpl
friend class MyClass;
threadid_t owning_thread;
public:
void doSomethingThreadSafe();
void doSomethingNoSafetyCheck();
;
class MyClass
MyClassImpl* impl;
public:
void doSomethine()
if (__threadid() != impl->owning_thread)
impl->doSomethingThreadSafe();
else
impl->doSomethingNoSafetyCheck();
;
注意:我知道 OP 想要列出具有活动指针的线程,我认为这是不可行的。上述实现至少让对象知道何时可能存在争用。何时更改 owning_thread 在很大程度上取决于 doSomething 的作用。
【讨论】:
【参考方案3】:通常您不能以编程方式执行此操作。
不幸的是,要走的路是设计你的程序,这样你就可以证明(即说服自己)某些对象是共享的,而其他对象是线程私有的。
当前的 C++ 标准甚至没有线程的概念,因此特别是没有线程本地存储的标准可移植概念。
【讨论】:
我愿意为我的目的使用 __thread 和 boost::thread_specific_ptr。我将编辑我的原始帖子以反映这一点。不过,我认为您总体上仍然是正确的。 如果你找到了聪明的方法,请告诉我。很抱歉,我无法在这里为您提供更具体的帮助。【参考方案4】:如果我正确理解了您的问题,我相信这可以在 Windows 中使用 Win32 函数 GetCurrentThreadId() 完成。 下面是如何使用它的一个快速而肮脏的例子。线程同步应该使用锁对象来完成。
如果您在要跟踪线程的对象的每个成员函数的顶部创建一个 CMyThreadTracker 对象,_handle_vector
应该包含使用您的对象的线程 ID。
#include <process.h>
#include <windows.h>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;
class CMyThreadTracker
vector<DWORD> & _handle_vector;
DWORD _h;
CRITICAL_SECTION &_CriticalSection;
public:
CMyThreadTracker(vector<DWORD> & handle_vector,CRITICAL_SECTION &crit):_handle_vector(handle_vector),_CriticalSection(crit)
EnterCriticalSection(&_CriticalSection);
_h = GetCurrentThreadId();
_handle_vector.push_back(_h);
printf("thread id %08x\n",_h);
LeaveCriticalSection(&_CriticalSection);
~CMyThreadTracker()
EnterCriticalSection(&_CriticalSection);
vector<DWORD>::iterator ee = remove_if(_handle_vector.begin(),_handle_vector.end(),bind2nd(equal_to<DWORD>(), _h));
_handle_vector.erase(ee,_handle_vector.end());
LeaveCriticalSection(&_CriticalSection);
;
class CMyObject
vector<DWORD> _handle_vector;
public:
void method1(CRITICAL_SECTION & CriticalSection)
CMyThreadTracker tt(_handle_vector,CriticalSection);
printf("method 1\n");
EnterCriticalSection(&CriticalSection);
for(int i=0;i<_handle_vector.size();++i)
printf(" this object is currently used by thread %08x\n",_handle_vector[i]);
LeaveCriticalSection(&CriticalSection);
;
CMyObject mo;
CRITICAL_SECTION CriticalSection;
unsigned __stdcall ThreadFunc( void* arg )
unsigned int sleep_time = *(unsigned int*)arg;
while ( true)
Sleep(sleep_time);
mo.method1(CriticalSection);
_endthreadex( 0 );
return 0;
int _tmain(int argc, _TCHAR* argv[])
HANDLE hThread;
unsigned int threadID;
if (!InitializeCriticalSectionAndSpinCount(&CriticalSection, 0x80000400) )
return -1;
for(int i=0;i<5;++i)
unsigned int sleep_time = 1000 *(i+1);
hThread = (HANDLE)_beginthreadex( NULL, 0, &ThreadFunc, &sleep_time, 0, &threadID );
printf("creating thread %08x\n",threadID);
WaitForSingleObject( hThread, INFINITE );
return 0;
编辑1: 如评论中所述,参考分配可以按如下方式实施。向量可以保存引用您的对象的唯一线程 ID。您可能还需要实现自定义赋值运算符来处理被不同线程复制的对象引用。
class MyClass
public:
static MyClass & Create()
static MyClass * p = new MyClass();
return *p;
static void Destroy(MyClass * p)
delete p;
private:
MyClass()
~MyClass();
;
class MyCreatorClass
MyClass & _my_obj;
public:
MyCreatorClass():_my_obj(MyClass::Create())
MyClass & GetObject()
//TODO:
// use GetCurrentThreadId to get thread id
// check if the id is already in the vector
// add this to a vector
return _my_obj;
~MyCreatorClass()
MyClass::Destroy(&_my_obj);
;
int _tmain(int argc, _TCHAR* argv[])
MyCreatorClass mcc;
MyClass &o1 = mcc.GetObject();
MyClass &o2 = mcc.GetObject();
return 0;
【讨论】:
这种解决方案的问题在于,新线程可能会保持对对象的引用而不调用其任何方法,直到经过任意时间。如果我想在 ref 计数达到某个值时采取任何形式的操作,那将是一个问题,因为该值可能在一段时间内是错误的。我可以编写一个自定义的 start_new_thread 函数来确保调用对象上的某些方法,但这并不能解决包含我的对象的对象的问题。 我猜在这种情况下,您可以实现工厂模式分配对您的对象的引用。并在工厂函数中记录线程id。 @JG 哇,这是可以理解的,但是当您需要计算未使用的引用时会变得更加棘手。 Indeera 的工厂想法可能会更好。不过,像指针策略这样的引用计数可能是可能的,就像一个想法的萌芽一样。【参考方案5】:我熟悉的解决方案是声明“如果您不使用正确的 API 与此对象进行交互,那么所有赌注都将失败。”
您也许可以改变您的要求,并使任何引用对象subscribe to signals from the object 的线程成为可能。这对竞争条件没有帮助,但允许线程知道对象何时自行卸载(例如)。
【讨论】:
【参考方案6】:要解决“我有一个对象,想知道有多少线程访问它”的问题,并且你也可以枚举你的线程,你可以用线程本地存储来解决这个问题。 为您的对象分配 TLS 索引。创建一个名为“registerThread”的私有方法,它只是将线程 TLS 设置为指向您的对象。
对发帖人最初想法的关键扩展是,在every方法调用期间,调用这个registerThread()。然后,您无需检测线程的创建时间或创建者,它只是在每次实际访问期间设置(通常是冗余的)。
要查看哪些线程访问了对象,只需检查它们的 TLS 值。
优点:简单而高效。
缺点:解决了发布的问题,但不能顺利扩展到多个对象或不可枚举的动态线程。
【讨论】:
不幸的是,这不起作用,请参阅我对 Indeera 答案的评论。以上是关于检测何时将对象传递给 C++ 中的新线程?的主要内容,如果未能解决你的问题,请参考以下文章
在Kotlin中,如果将对象传递给类的新实例,然后更改某些属性,它是否会在原始对象中更改?