Обнаружение, когда объект передается в новый поток в C++?


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

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

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

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

6 2

6 ответов:

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

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

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

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

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

Вы можете начать ограничивать то, что пользователи могут и не могут делать с объектом, но я не думаю, что стоит охватывать больше, чем очевидные источники непреднамеренных ошибок. Должны ли вы объявлять operator & private, чтобы люди не могли принимать указатели на ваши объекты? Должны ли вы препятствовать людям динамически распределять ваш объект? Это в какой-то степени зависит от ваших пользователей, но имейте в виду, что вы не можете предотвратить ссылки на объекты, поэтому в конечном итоге игра в whack-a-mole сведет вас с ума. Итак, вернемся к моему первоначальному предложению: по возможности проанализируйте общую картину.

Если не считать реализации стиля pimpl, которая выполняет проверку threadid перед каждым разыменованием, я не вижу, как вы могли бы это сделать:

 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.

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

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

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

Если я правильно понял вашу проблему, я думаю, что это можно сделать в 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;
}

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

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

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

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

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

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

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