检测对象何时传递给 C++ 中的新线程?

发布于 2024-07-25 08:49:15 字数 691 浏览 8 评论 0原文

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

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

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

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

I have an object for which I'd like to track the number of threads that reference it. In general, when any method on the object is called I can check a thread local boolean value to determine whether the count has been updated for the current thread. But this doesn't help me if the user say, uses boost::bind to bind my object to a boost::function and uses that to start a boost::thread. The new thread will have a reference to my object, and may hold on to it for an indefinite period of time before calling any of its methods, thus leading to a stale count. I could write my own wrapper around boost::thread to handle this, but that doesn't help if the user boost::bind's an object that contains my object (I can't specialize based on the presence of a member type -- at least I don't know of any way to do that) and uses that to start a boost::thread.

Is there any way to do this? The only means I can think of requires too much work from users -- I provide a wrapper around boost::thread that calls a special hook method on the object being passed in provided it exists, and users add the special hook method to any class that contains my object.

Edit: For the sake of this question we can assume I control the means to make new threads. So I can wrap boost::thread for example and expect that users will use my wrapped version, and not have to worry about users simultaneously using pthreads, etc.

Edit2: One can also assume that I have some means of thread local storage available, through __thread or boost::thread_specific_ptr. It's not in the current standard, but hopefully will be soon.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

深者入戏 2024-08-01 08:49:15

一般来说,这很难。 “谁有我的参考资料?”的问题。 在 C++ 中通常无法解决。 可能值得从更大的角度考虑您正在尝试解决的特定问题,并看看是否有更好的方法。

我可以想出一些可以帮助您实现目标的方法,但没有一个是您想要的。

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

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

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

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

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

In general, this is hard. The question of "who has a reference to me?" is not generally solvable in C++. It may be worth looking at the bigger picture of the specific problem(s) you are trying to solve, and seeing if there is a better way.

There are a few things I can come up with that can get you partway there, but none of them are quite what you want.

You can establish the concept of "the owning thread" for an object, and REJECT operations from any other thread, a la Qt GUI elements. (Note that trying to do things thread-safely from threads other than the owner won't actually give you thread-safety, since if the owner isn't checked it can collide with other threads.) This at least gives your users fail-fast behavior.

You can encourage reference counting by having the user-visible objects being lightweight references to the implementation object itself [and by documenting this!]. But determined users can work around this.

And you can combine these two-- i.e. you can have the notion of thread ownership for each reference, and then have the object become aware of who owns the references. This could be very powerful, but not really idiot-proof.

You can start restricting what users can and cannot do with the object, but I don't think covering more than the obvious sources of unintentional error is worthwhile. Should you be declaring operator& private, so people can't take pointers to your objects? Should you be preventing people from dynamically allocating your object? It depends on your users to some degree, but keep in mind you can't prevent references to objects, so eventually playing whack-a-mole will drive you insane.

So, back to my original suggestion: re-analyze the big picture if possible.

倦话 2024-08-01 08:49:15

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

Short of a pimpl style implementation that does a threadid check before every dereference I don't see how you could do this:

 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();
         }
     }
 };

Note: I know the OP wants to list threads with active pointers, I don't think that's feasible. The above implementation at least lets the object know when there might be contention. When to change the owning_thread depends heavily on what doSomething does.

淡淡绿茶香 2024-08-01 08:49:15

通常您无法以编程方式执行此操作。

不幸的是,正确的方法是以这样的方式设计您的程序:您可以证明(即说服自己)某些对象是共享的,而其他对象是线程私有的。

当前的 C++ 标准甚至没有线程的概念,因此特别没有线程本地存储的标准可移植概念。

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.

伴我心暖 2024-08-01 08:49:15

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

If I understood your problem correctly I believe this could be done in Windows using Win32 function GetCurrentThreadId().
Below is a quick and dirty example of how it could be used. Thread synchronisation should rather be done with a lock object.

If you create an object of CMyThreadTracker at the top of every member function of your object to be tracked for threads, the _handle_vector should contain the thread ids that use your object.

#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:
As mentioned in the comment, reference dispensing could be implemented as below. A vector could hold the unique thread ids referring to your object. You may also need to implement a custom assignment operator to deal with the object references being copied by a different thread.

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;
}
梦境 2024-08-01 08:49:15

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

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

The solution I'm familiar with is to state "if you don't use the correct API to interact with this object, then all bets are off."

You may be able to turn your requirements around and make it possible for any threads that reference the object subscribe to signals from the object. This won't help with race conditions, but allows threads to know when the object has unloaded itself (for instance).

↙厌世 2024-08-01 08:49:15

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

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

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

优点:简单且相当高效。

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

To solve the problem "I have an object and want to know how many threads access it" and you also can enumerate your threads, you can solve this problem with thread local storage.
Allocate a TLS index for your object. Make a private method called "registerThread" which simply sets the thread TLS to point to your object.

The key extension to the poster's original idea is that during every method call, call this registerThread(). Then you don't need to detect when or who created the thread, it's just set (often redundantly) during every actual access.

To see which threads have accessed the object, just examine their TLS values.

Upside: simple and pretty efficient.

Downside: solves the posted question but doesn't extend smoothly to multiple objects or dynamic threads that aren't enumerable.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文