2

我有一个对象,我想跟踪引用它的线程数。通常,当调用对象上的任何方法时,我可以检查线程本地布尔值以确定当前线程的计数是否已更新。但是,如果用户说使用 boost::bind 将我的对象绑定到 boost::function 并使用它来启动 boost::thread,这对我没有帮助。新线程将引用我的对象,并且在调用它的任何方法之前可能会无限期地持有它,从而导致过时的计数。我可以围绕 boost::thread 编写自己的包装器来处理这个问题,但是如果用户 boost::bind 的对象包含我的对象(我不能根据成员类型的存在进行专门化——至少我不知道有什么方法可以做到这一点)并使用它来启动 boost::thread。

有没有办法做到这一点?我能想到的唯一方法需要用户做太多工作——我提供了一个围绕 boost::thread 的包装器,它在传入的对象上调用一个特殊的钩子方法,前提是它存在,并且用户将这个特殊的钩子方法添加到任何类包含我的对象。

编辑:为了这个问题,我们可以假设我控制了制作新线程的方法。所以我可以包装 boost::thread 例如并期望用户会使用我的包装版本,而不必担心用户同时使用 pthread 等。

Edit2:也可以假设我有一些可用的线程本地存储方法,通过__threadboost::thread_specific_ptr。它不在当前标准中,但希望很快就会出现。

4

6 回答 6

3

一般来说,这很难。“谁提到了我?”的问题。在 C++ 中通常无法解决。可能值得查看您正在尝试解决的特定问题的更大图景,看看是否有更好的方法。

我可以想出一些可以让你成功的方法,但它们都不是你想要的。

您可以为对象建立“拥有线程”的概念,并从任何其他线程(如 Qt GUI 元素)中拒绝操作。(请注意,尝试从所有者以外的线程安全地执行操作实际上不会给您线程安全,因为如果不检查所有者,它可能会与其他线程发生冲突。)这至少会让您的用户失败-快速的行为。

您可以通过让用户可见的对象成为对实现对象本身的轻量级引用来鼓励引用计数[并记录下来!]。但坚定的用户可以解决这个问题。

您可以将这两者结合起来——也就是说,您可以拥有每个引用的线程所有权的概念,然后让对象知道谁拥有这些引用。这可能非常强大,但并不是真正的白痴。

您可以开始限制用户可以和不能对对象执行的操作,但我认为不值得覆盖明显的意外错误来源。您是否应该声明 operator& 私有,这样人们就无法获取指向您的对象的指针?您是否应该阻止人们动态分配您的对象?这在某种程度上取决于您的用户,但请记住,您无法阻止对对象的引用,因此最终玩 whack-a-mole 会让您发疯。

所以,回到我最初的建议:如果可能,重新分析全局。

于 2009-06-26T16:33:23.663 回答
1

Usually you cannot do this programmatically.

Unfortuately, the way to go is to design your program in such a way that you can prove (i.e. convince yourself) that certain objects are shared, and others are thread private.

The current C++ standard does not even have the notion of a thread, so there is no standard portable notion of thread local storage, in particular.

于 2009-06-26T18:35:39.210 回答
1

缺少在每次取消引用之前执行 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 的作用。

于 2009-06-26T15:28:17.373 回答
0

为了解决“我有一个对象,想知道有多少线程访问它”的问题,你也可以枚举你的线程,你可以用线程本地存储来解决这个问题。为您的对象分配 TLS 索引。创建一个名为“registerThread”的私有方法,它只是将线程 TLS 设置为指向您的对象。

发帖人最初想法的关键扩展是,在每个方法调用期间,调用这个 registerThread()。然后您不需要检测何时或谁创建了线程,它只是在每次实际访问期间设置(通常是冗余的)。

要查看哪些线程访问了该对象,只需检查它们的 TLS 值。

优点:简单且非常有效。

缺点:解决了发布的问题,但不能顺利扩展到多个对象或不可枚举的动态线程。

于 2009-06-30T16:23:43.747 回答
0

我熟悉的解决方案是声明“如果您不使用正确的 API 与该对象进行交互,那么所有的赌注都将失败。”

您也许可以改变您的要求,使任何引用该对象的线程都可以订阅来自该对象的信号。这对竞争条件没有帮助,但允许线程知道对象何时自行卸载(例如)。

于 2009-06-26T23:54:28.917 回答
0

如果我正确理解了您的问题,我相信这可以在 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;
}

EDIT1:如评论中所述,参考分配可以如下实施。向量可以保存引用您的对象的唯一线程 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;
}
于 2009-06-26T20:15:42.447 回答