2009-06-26 45 views
2

我有一個對象,我想跟蹤引用它的線程數。通常,當調用對象上的任何方法時,我可以檢查線程本地布爾值以確定當前線程的計數是否已更新。但是,如果用戶說,使用boost :: bind將我的對象綁定到boost :: function並使用它來啓動boost :: thread,這並不能幫助我。新線程會引用我的對象,並可能在調用其任何方法之前無限期地保留它,從而導致陳舊的計數。我可以在boost :: thread中編寫自己的包裝來處理這個問題,但如果用戶boost :: bind的對象包含我的對象(我不能基於成員類型的存在進行特殊化 - 至少我不知道有什麼辦法做到這一點),並使用它來啓動一個boost :: thread。檢測對象何時傳遞給C++中的新線程?

有沒有辦法做到這一點?我能想到的唯一方法需要用戶做太多的工作 - 我提供了一個boost :: thread的包裝器,它在要傳入的對象上調用一個特殊的鉤子方法,只要它存在,並且用戶將特殊鉤子方法添加到任何類包含我的對象。

編輯:爲了這個問題,我們可以假設我控制了創建新線程的方法。因此,我可以包裝boost :: thread例如,並期望用戶將使用我的包裝版本,而不必擔心用戶同時使用pthreads等。

編輯2:也可以假設我有一些線程的手段本地存儲可用,通過__threadboost::thread_specific_ptr。這不是目前的標準,但希望很快。

+1

我認爲解決您的問題非常困難。這是因爲引用是指向另一個內存位置的內存或內存(+或多或少的額外內容)。因爲內存在線程之間共享,所以你不能分辨哪個線程擁有引用,只是因爲所有線程都「擁有」內存,它們都擁有引用。這並不意味着這是不可能的,但是由於技術基礎總是分享內容,所以很難做到。 – mmmmmmmm 2009-06-26 15:38:17

+0

是的,我希望通過控制線程的創建方式,我可以以某種方式解決這個問題。我編輯了我的帖子,使這種可能性明確。 – 2009-06-26 15:45:41

回答

3

一般來說,這很難。 「誰提及我?」的問題通常不能用C++解決。可能值得關注一下您正在努力解決的具體問題的全貌,並看看是否有更好的方法。

有幾件事我可以拿出來,可以讓你在那裏,但沒有一個是你想要的。

您可以爲對象建立「擁有線程」的概念,並從任何其他線程(一個la Qt GUI元素)中建立REJECT操作。 (請注意,試圖從線程安全地執行線程而不是所有者線程並不會真正爲線程安全,因爲如果未檢查所有者,它可能會與其他線程發生衝突。)這至少爲用戶提供了故障轉移功能,快速的行爲。

您可以鼓勵引用計數,方法是讓用戶可見對象是實現對象本身的輕量級引用[並通過記錄此!]。但是確定的用戶可以解決這個問題。

你可以結合這兩個 - 即你可以擁有線程所有權的概念,每個參考,然後讓對象知道誰擁有引用。這可能是非常強大的,但不是真正的白癡證明。

你可以開始限制哪些用戶可以和不可以與對象做,但我不認爲不覆蓋非故意的錯誤明顯的來源更是值得的。你應該聲明運營商&私人,所以人們不能指望你的對象?你應該阻止人們動態分配你的對象嗎?這取決於你的用戶在某種程度上,但請記住,你不能阻止引用的對象,所以最終玩捶鼴鼠會讓你瘋狂。

所以,回到我原來的建議:如果可能的話,重新分析大圖。

1

短一PIMPL風格實現,做了主題ID檢查每次提領之前,我不知道你怎麼能做到這一點的:

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要列出與活躍的指針線程,我認爲這不可行。上面的實現至少讓對象知道何時可能存在爭用。什麼時候改變own_thread很大程度上取決於某些東西的作用。

1

通常你不能以編程方式執行此操作。

Unfortuately,要走的路是這樣一種方式,你可以證明你的設計方案(即說服自己),某些對象是共用的,其他都是線程專用。

當前的C++標準甚至沒有線程的概念,所以沒有特別的線程本地存儲的標準可移植概念。

+0

我願意爲我的目的使用__thread和boost :: thread_specific_ptr。我會編輯我原來的帖子來反映這一點。儘管如此,我認爲你仍然是正確的。 – 2009-06-26 18:58:27

+0

如果你發現一個聰明的方式,讓我知道。對不起,我不能在這裏給你更具體的幫助。 – Tobias 2009-06-26 19:12:16

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; 
} 
0

我熟悉的解決方案是聲明「如果您沒有使用正確的API與此對象進行交互,那麼所有投注都將關閉。」

您可以扭轉你的需求,並有可能對指向對象subscribe to signals from the object任何線程。這對競爭條件沒有幫助,但允許線程知道對象何時已經卸載(例如)。

0

要解決「我有一個對象並想知道有多少線程訪問它」的問題,並且您還可以枚舉線程,可以使用線程本地存儲來解決此問題。 爲您的對象分配一個TLS索引。創建一個名爲「registerThread」的私有方法,它只需將線程TLS設置爲指向您的對象。

對於海報的原始想法的關鍵擴展是在每個方法調用期間,調用此registerThread()。然後,您不需要檢測何時或誰創建線程,只需在每次實際訪問期間設置(通常是冗餘)即可。

要查看哪些線程訪問了該對象,只需檢查其TLS值。

上行鏈路:簡單而高效。

下行:解決發佈的問題,但不能平穩擴展到多個對象或不可枚舉的動態線程。