Обнаружение, когда объект передается в новый поток в C ++? - PullRequest
2 голосов
/ 26 июня 2009

У меня есть объект, для которого я хотел бы отслеживать количество потоков, ссылающихся на него. В общем, когда вызывается любой метод объекта, я могу проверить локальное логическое значение потока, чтобы определить, было ли обновлено число для текущего потока. Но это не поможет мне, если пользователь скажет, использует boost :: bind для привязки моего объекта к boost :: function и использует это для запуска boost :: thread. Новый поток будет иметь ссылку на мой объект и может удерживать его в течение неопределенного периода времени перед вызовом любого из его методов, что приведет к отсроченному счету. Я мог бы написать свою собственную обертку вокруг boost :: thread, чтобы справиться с этим, но это не поможет, если пользователь boost :: bind является объектом , который содержит моего объекта (я не могу специализироваться на основании присутствия типа члена - по крайней мере, я не знаю, как это сделать) и использует его для запуска boost :: thread.

Есть ли способ сделать это? Единственное средство, которое я могу придумать, требует слишком много работы от пользователей - я предоставляю обертку вокруг boost :: thread, которая вызывает специальный метод ловушки для передаваемого объекта, если он существует, и пользователи добавляют специальный метод ловушки в любой класс который содержит мой объект.

Редактировать: Ради этого вопроса мы можем предположить, что я контролирую средства для создания новых тем. Так что я могу, например, обернуть boost :: thread и ожидать, что пользователи будут использовать мою упакованную версию, и мне не придется беспокоиться о пользователях, одновременно использующих pthreads и т. Д.

Edit2: Можно также предположить, что у меня есть некоторые средства локального хранилища потоков, через __thread или boost::thread_specific_ptr. Это не соответствует текущему стандарту, но, надеюсь, скоро будет.

Ответы [ 6 ]

3 голосов
/ 26 июня 2009

В общем, это сложно. Вопрос "у кого есть ссылка на меня?" обычно не решаемо в C ++. Возможно, стоит взглянуть на более широкую картину конкретной проблемы, которую вы пытаетесь решить, и посмотреть, есть ли лучший способ.

Есть несколько вещей, которые я могу придумать, которые помогут вам на полпути, но ни одна из них не совсем то, что вы хотите.

Вы можете установить понятие "владеющий поток" для объекта и операции REJECT из любого другого потока, например, элементов Qt GUI. (Обратите внимание, что попытка сделать вещи потокобезопасными из потоков, отличных от владельца, на самом деле не обеспечит вам безопасность потоков, поскольку, если владелец не проверен, он может столкнуться с другими потоками.) Это, по крайней мере, дает вашим пользователям сбой. быстрое поведение.

Вы можете поощрять подсчет ссылок, когда видимые пользователем объекты являются легкими ссылками на сам объект реализации [и документируя это!]. Но решительные пользователи могут обойти это.

И вы можете объединить эти два - то есть у вас может быть понятие владения потоком для каждой ссылки , и затем объект узнает, кто владеет ссылками. Это может быть очень мощным, но на самом деле не идиотостойким.

Вы можете начать ограничивать то, что пользователи могут и не могут делать с объектом, но я не думаю, что стоит охватывать больше, чем очевидные источники непреднамеренной ошибки. Должны ли вы объявить оператора и частного, чтобы люди не могли указатели на ваши объекты? Должны ли вы мешать людям динамически распределять ваш объект? В некоторой степени это зависит от ваших пользователей, но имейте в виду, что вы не можете запретить ссылки на объекты, поэтому в конечном итоге игра в удар-моль сведет вас с ума.

Итак, вернемся к моему первоначальному предложению: повторно проанализируйте общую картину, если это возможно.

1 голос
/ 26 июня 2009

Обычно вы не можете сделать это программно.

К сожалению, путь состоит в том, чтобы разработать свою программу таким образом, чтобы вы могли доказать (то есть убедить себя), что некоторые объекты являются общими, а другие являются частными.

Текущий стандарт C ++ даже не имеет понятия о потоке, поэтому, в частности, стандартного переносимого понятия о локальном хранилище потока не существует.

1 голос
/ 26 июня 2009

Если не считать реализацию в стиле 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();
         }
     }
 };

Примечание: я знаю, что ОП хочет перечислить темы с активными указателями, я не думаю, что это осуществимо. Вышеуказанная реализация, по крайней мере, позволяет объекту знать, когда может возникнуть конфликт. Когда изменять owning_thread, сильно зависит от того, что делает doSomething.

0 голосов
/ 30 июня 2009

Чтобы решить проблему «У меня есть объект и я хочу знать, сколько потоков к нему обращается», и вы также можете перечислить свои потоки, вы можете решить эту проблему с помощью локального хранилища потоков. Выделите индекс TLS для вашего объекта. Создайте закрытый метод с именем «registerThread», который просто устанавливает поток TLS, чтобы он указывал на ваш объект.

Ключевое расширение первоначальной идеи автора заключается в том, что при каждом вызове метода вызывайте this registerThread (). Тогда вам не нужно определять, когда или кто создал поток, он просто устанавливается (часто избыточно) во время каждого фактического доступа.

Чтобы увидеть, какие потоки получили доступ к объекту, просто проверьте их значения TLS.

Вверх: просто и довольно эффективно.

Недостаток: решает опубликованный вопрос, но не распространяется гладко на несколько объектов или динамических потоков, которые не перечисляются.

0 голосов
/ 27 июня 2009

Решение, с которым я знаком, состоит в том, чтобы заявить: «Если вы не используете правильный API для взаимодействия с этим объектом, тогда все ставки выключены».

Вы можете изменить свои требования и сделать возможным для любых потоков, ссылающихся на объект , подписаться на сигналы от объекта . Это не поможет в условиях гонки, но позволит потокам узнать, когда объект выгружен сам (например).

0 голосов
/ 27 июня 2009

Если я правильно понял вашу проблему, я считаю, что это можно сделать в Windows с помощью функции Win32 GetCurrentThreadId () . Ниже приведен быстрый и грязный пример того, как его можно использовать. Синхронизацию потоков лучше выполнять с помощью объекта блокировки.

Если вы создаете объект CMyThreadTracker в верхней части каждой функции-члена вашего объекта, который будет отслеживаться для потоков, _handle_vector должен содержать идентификаторы потоков, которые используют ваш объект.

#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: Как упомянуто в комментарии, выдача справок может быть осуществлена, как показано ниже. Вектор может содержать уникальные идентификаторы потоков, ссылающиеся на ваш объект. Вам также может понадобиться реализовать пользовательский оператор присваивания для обработки ссылок на объекты, копируемых другим потоком.

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