#c 11 #shared-ptr #refcounting
#c 11 #shared-ptr #повторный подсчет
Вопрос:
Недавно я придумал хитрый план (tm : P)) Мне приходится обновлять структуру настроек в моей программе (скажем, каждые 15 секунд). Структура настроек используется несколькими функциями, и каждая из этих функций вызывается несколькими потоками. Итак, мне нужен счетчик ссылок, чтобы знать, когда безопасно освобождать старую структуру настроек. Итак, это правильный способ сделать это? Пожалуйста, не отвечайте, что это нормально, если вы невнимательно читали код, когда дело доходит до общих указателей, при подобных злоупотреблениях легко допустить ошибки (поверьте мне). РЕДАКТИРОВАТЬ: Я забыл упомянуть важную часть. Я думаю, что эта реализация предотвращает падение счетчика ссылок до 0, потому что я инициализирую его в updateSettings (), и он не падает, пока не будет вызван снова (и тогда myFucntion использует другие из двух настроек в памяти).
#include<memory>
#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
struct STNGS
{
int i;
vector<double> v;
};
static int CUR_STNG=0;
shared_ptr<STNGS> stngsArray[2];
int myFunction() //called by multiple threads
{
shared_ptr<STNGS> pStngs=stngsArray[CUR_STNG];
STNGSamp; stngs=*pStngs;
//do some stuff using stngs
}
void updateSettings()
{
auto newIndex=(CUR_STNG 1)%2;
stngsArray[newIndex].reset(new STNGS);
CUR_STNG=newIndex;
}
void initialize()
{
auto newIndex=CUR_STNG;
stngsArray[newIndex].reset(new STNGS);
CUR_STNG=newIndex;
}
int main()
{
initialize();
//launch bunch of threads that are calling myFunction
while(true)
{
//call updateSettings every 15 seconds
}
}
РЕДАКТИРОВАТЬ: используя отзывы из комментариев, я обновил код:
#include<memory>
#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
static const int N_STNG_SP=4;
static int CUR_STNG=0;
struct STNGS
{
int i;
vector<double> v;
STNGS()
{
for (int i=0;i<10; i)
v.push_back(42);
}
};
shared_ptr<STNGS> stngs[N_STNG_SP];
int myFunction() //called by multiple threads
{
shared_ptr<STNGS> pStngs=stngs[CUR_STNG];
STNGSamp; stngs=*pStngs;
//do some stuff using stngs
}
void updateSettings()
{
auto pStng=new STNGS;
//fill *pStng
int newVer=(CUR_STNG 1)%N_STNG_SP;
stngs[newVer].reset(pStng);
CUR_STNG=newVer;
}
void initialize()
{
auto pStng=new STNGS;
//fill *pStng
int newVer=(CUR_STNG 1)%N_STNG_SP;
stngs[newVer].reset(pStng);
CUR_STNG=newVer;
}
int main()
{
initialize();
//launch bunch of threads that are calling myFunction
while(true)
{
//call updateSettings every 15 seconds
updateSettings();
}
}
Ответ №1:
Я бы не доверял этому коду. Я считаю, что не хватает надлежащих барьеров памяти для всей памяти, совместно используемой разными потоками, за исключением двух счетчиков ссылок.
На мой взгляд, это хорошее приложение для shared_mutex.
Редактировать:
20.7.2.2 [util.smartptr.shared]/p4 говорит:
В целях определения наличия гонки данных функции-члены должны получать доступ и изменять только сами объекты shared_ptr и weak_ptr, а не объекты, на которые они ссылаются.
Однако вместо использования shared_mutex другим вариантом может быть использование API в 20.7.2.5 атомарный доступ shared_ptr [util.smartptr.shared.atomic]:
Одновременный доступ к объекту shared_ptr из нескольких потоков не приводит к гонке данных, если доступ осуществляется исключительно через функции в этом разделе и экземпляр передается в качестве их первого аргумента.
template<class T>
bool atomic_is_lock_free(const shared_ptr<T>* p);
template<class T>
shared_ptr<T> atomic_load(const shared_ptr<T>* p);
template<class T>
shared_ptr<T> atomic_load_explicit(const shared_ptr<T>* p, memory_order mo);
template<class T>
void atomic_store(shared_ptr<T>* p, shared_ptr<T> r);
template<class T>
void atomic_store_explicit(shared_ptr<T>* p, shared_ptr<T> r, memory_order mo);
template<class T>
shared_ptr<T> atomic_exchange(shared_ptr<T>* p, shared_ptr<T> r);
template<class T>
shared_ptr<T>
atomic_exchange_explicit(shared_ptr<T>* p, shared_ptr<T> r, memory_order mo);
template<class T>
bool
atomic_compare_exchange_weak(shared_ptr<T>* p, shared_ptr<T>* v, shared_ptr<T> w);
template<class T>
bool
atomic_compare_exchange_strong( shared_ptr<T>* p, shared_ptr<T>* v, shared_ptr<T> w);
template<class T>
bool
atomic_compare_exchange_weak_explicit(shared_ptr<T>* p, shared_ptr<T>* v,
shared_ptr<T> w, memory_order success,
memory_order failure);
template<class T>
bool
atomic_compare_exchange_strong_explicit(shared_ptr<T>* p, shared_ptr<T>* v,
shared_ptr<T> w, memory_order success,
memory_order failure);
shared_mutex будет легче получить правильный. Но атомарный API shared_ptr может дать решение с более высокой производительностью.
Обновить:
Вот непроверенный код для решения shared_mutex (обратите внимание, shared_mutex — это не std, а библиотека стороннего разработчика):
struct STNGS
{
int i;
vector<double> v;
ting::shared_mutex m;
};
STNGS stngs;
int myFunction() //called by multiple threads
{
shared_lock<shared_mutex> _(stngs.m);
//do some stuff using stngs
return 0;
}
void updateSettings()
{
unique_lock<shared_mutex> _(stngs.m);
//fill stngs
}
void initialize()
{
//fill stngs
}
Вот непроверенный код, который использует атомарные функции загрузки / сохранения для shared_ptr:
struct STNGS
{
int i;
vector<double> v;
};
shared_ptr<STNGS> pStng;
int myFunction() //called by multiple threads
{
shared_ptr<STNGS> stngs = atomic_load(amp;pStng);
//do some stuff using *stngs
return 0;
}
void updateSettings()
{
shared_ptr<STNGS> newStng(new STNGS);
//fill *newStng
atomic_store(amp;pStng, newStng);
}
void initialize()
{
pStng.reset(new STNGS);
//fill *pStng
}
Комментарии:
1. О, другого ответа больше нет… Но спросить еще раз. Зачем вам atomic_load? Я понимаю, что требуется атомарное хранилище, но разве атомарное хранилище не гарантирует atomic_load? Я имею в виду, заблокирована ли переменная;t? И, как я уже сказал, другие функции только считывают настройки. Если это глупый вопрос, пожалуйста, дайте мне несколько ссылок. Говоря о глупом вопросе: теперь я вижу, что общий ptr не копирует значение, если необработанный указатель используется в качестве аргумента для конструктора. Для меня это ново ( : P), поэтому у меня все еще нет мнения о том, является ли это хорошей идеей по стандарту или нет.
2. Считайте атомарные инструкции не чем иным, как мьютексом, обернутым вокруг указанной операции (что на самом деле и делает реализация boost). Каждый shared_ptr состоит из двух указателей, которые должны быть прочитаны / записаны последовательно, в дополнение к обновлению количества ссылок. Если поток, который устанавливает shared_ptr, получает только один из указателей, установленных до того, как читатель прочитает тот же shared_ptr, у вас проблемы. Таким образом, как reader, так и writer должны синхронизироваться друг с другом. atomic_load блокирует мьютекс под капотом, чтобы автор не мог установить его во время чтения.
3. Хорошо, я понял. Видите ли вы какие-то проблемы с моим решением «циклический массив общих указателей». Я не понимаю, как это может привести к сбою, потому что изменяется только один индекс, и он должен быть «довольно атомарным» :). Возможно, что функция занимает много времени, и я начинаю обновлять некоторый элемент циклического массива (queue), пока он все еще используется, но если мы выбрали большой размер для queue, все должно быть в порядке.
4. Я действительно не уверен, является ли ваше решение надежным или нет. Вы читаете и записываете CUR_STNG вне какой-либо синхронизации памяти. Обычно я бы просто остановился на этом и сказал «сломан». Однако можно утверждать, что изменение количества ссылок в shared_ptr должно обеспечить достаточный барьер памяти, чтобы сделать CUR_STNG видимым также для каждого потока. Я недостаточно опытен, чтобы быть уверенным в том или ином. Я знаю лишь горстку людей, которые. Я предполагаю, что вы используете аппаратное обеспечение x86 и что это всегда будет работать на x86. Но я был бы в ужасе запускать этот код в другом месте.
5. Tnx, да, я на x86. Я всегда думал, что запись и чтение таким образом безопасны, потому что это всего лишь один writer и это всего лишь один int. И когда считывается CUR_STNG, это означает, что запись этих настроек завершена (потому что CUR_STNG обновляется после завершения записи настроек). но я знаю, что это совершенно другой вопрос, а не исходный, поэтому я благодарю вас за ваше время и ваш ответ.