boost :: shared_ptr и многопоточный доступ - PullRequest
2 голосов
/ 20 июля 2009

Я пытаюсь реализовать многопоточный фреймворк, в котором выходные объекты создаются в конце каждого фрейма, который запускает мой сетевой поток, чтобы другой поток мог в начале своего фрейма получить самый последний завершенный выходной "указатель и знайте, что он имеет безопасный и полный доступ только для чтения к любым данным, хранящимся в выходном объекте.

Мой (очень ранний) подход к этому прежде всего включает следующие блоки кода:

NetworkHandler -

void NetworkHandler::Tick()
    {
        // Tick on our io_service
        m_ios.poll();

        // Assemble new output data
        this->AssembleOutput();
    }

ClientNetworkHandler -

void ClientNetworkHandler::AssembleOutput()
    {
        std::tr1::shared_ptr<crusade::task::TaskOutput> newOutput(new crusade::task::TaskOutput());
        newOutput->m_outputElements["connected"] = std::tr1::shared_ptr<crusade::task::TaskOutputElement>(new NetworkConnectedTaskOutputElement(this->m_isConnected));
        this->m_latestOutput.swap(newOutput);
    }

PyCruHandler -

void PyCruHandler::Tick()
    {
        printf("PyCruHandler\n");
        // Get any necessary inputs from other threads
        m_latestNetworkOutput.swap(crusade::task::THManager::GetInstance()->GetTaskHandler(crusade::task::THManager::TH_NETWORK)->GetLatestOutput());
        // Other unrelated processing to go here
    }

Обычно ClientNetworkHandler и PyCruHandler работают независимо в отдельных потоках. PyCruHandler фактически никогда ничего не делает со своей копией m_latestNetworkOutput; Я прокомментировал все остальные экземпляры, где к нему обращались каким-либо образом, и у меня все еще есть следующая проблема:

Если я позволю обоим потокам вызывать swap (или оператор = эквивалент), то в конечном итоге (обычно в течение 2 секунд после запуска, но иногда это занимает несколько минут) я получу следующую ошибку либо для оператора new, либо для allocator удаление какого-либо вида:

"HEAP: блок свободной кучи 2bab3b0, измененный на 2bab3dc после освобождения Windows сработала точка останова.

Это может быть связано с повреждением кучи, что указывает на ошибку ... и т. Д. "

Я только новичок, но мне кажется, что это указывает на некоторую проблему с проблемами безопасности потоков и доступа к времени между объектами shared_ptr. Тем не менее, я был смущен путаницей объяснений (как здесь, так и в других местах) нюансов потоковой безопасности shared_ptr - одна часть чтения указывает, что счетчик ссылок является поточно-ориентированным, поэтому безопасно копировать shared_ptrs вокруг (но их внутренние объекты) не будет потокобезопасным), другие части чтения указывают на то, что в shared_ptr нет никакой безопасности потоков, какой-либо полезности. Я прочитал дополнительную документацию по безопасности потоков в shared_ptrs, но это все еще не прояснило, должно ли это быть проблемой в моем коде.

Мой вопрос: может ли кто-нибудь здесь обнаружить какой-либо очевидный недостаток в том, что я делаю? Моя цель состоит в том, чтобы я мог получить доступ к последнему объекту вывода, все еще сохраненному потоком-владельцем, и тогда этот объект не будет удален (даже после того, как поток-владелец перейдет к более поздним выводам), пока каждый пользователь вывода не завершит его. также. Мне показалось, что общий указатель идеально подходит для этого ... но после 3 часов удара головой я начинаю удивляться ...

Заранее большое спасибо, и я прошу прощения, если я как-то написал это неправильно; Я впервые здесь, и часто задаваемые вопросы кажутся довольно непринужденными в том, что касается протокола!

Ответы [ 2 ]

4 голосов
/ 20 июля 2009

Вероятно, лучшим ресурсом здесь является документация :

Резьба безопасности

объекты shared_ptr предлагают то же самое уровень безопасности потока как встроенный типы. Экземпляр shared_ptr может быть «чтение» (доступ только с использованием const операции) одновременно несколькими потоки. Разное shared_ptr экземпляры могут быть «записаны» (доступ с использованием изменяемых операций например оператор = или сброс) одновременно несколькими потоками (даже когда эти экземпляры являются копиями, и использовать один и тот же счетчик ссылок внизу.)

Примеры:

shared_ptr<int> p(new int(42));

//--- Example 1 ---

// thread A
shared_ptr<int> p2(p); // reads p

// thread B
shared_ptr<int> p3(p); // OK, multiple reads are safe

//--- Example 2 ---

// thread A
p.reset(new int(1912)); // writes p

// thread B
p2.reset(); // OK, writes p2

//--- Example 3 ---

// thread A
p = p3; // reads p3, writes p

// thread B
p3.reset(); // writes p3; undefined, simultaneous read/write

//--- Example 4 ---

// thread A
p3 = p2; // reads p2, writes p3

// thread B
// p2 goes out of scope: undefined, the destructor is considered a "write access"

//--- Example 5 ---

// thread A
p3.reset(new int(1));

// thread B
p3.reset(new int(2)); // undefined, multiple writes

Я собираюсь предположить, что m_latestOutput в вашем коде - это shared_ptr - в этом случае пример, который вы используете, наиболее близок к цифре 5 (множественные записи).

1 голос
/ 20 июля 2009

Согласно документации, вы должны синхронизировать одновременное чтение и запись. Своп есть оба :). Попробуйте вместо этого использовать operator = (), это будет только запись.

Кроме того, ваши клиентские потоки должны сделать копию m_latestNetworkOutput (которая будет прочитана), если они хотят сохранить объект живым до тех пор, пока он не будет готов (я думаю, это то, что вы делаете в PyCruHandler :: Tick ()).

В любом случае, вам придется синхронизировать запись:

this->m_latestOutput.swap(newOutput);

И читать:

m_latestNetworkOutput.swap(crusade::task::THManager::GetInstance()->GetTaskHandler(crusade::task::THManager::TH_NETWORK)->GetLatestOutput());

И поменяйте своп на присвоение - вам не нужен старый указатель после свопа, не так ли?

...