2017-01-06 4 views
6
std::shared_ptr<int> int_ptr; 

int main() { 
    int_ptr = std::make_shared<int>(1); 
    std::thread th{[&]() { 
     std::weak_ptr int_ptr_weak = int_ptr; 
     auto int_ptr_local = int_ptr_weak.lock(); 
     if (int_ptr_local) { 
      cout << "Value in the shared_ptr is " << *int_ptr_local << endl; 
     } 
    }); 

    int_ptr.reset(nullptr); 
    th.join(); 
    return 0; 
} 

Является ли код над потоком безопасным? Я прочитал этот ответ About thread-safety of weak_ptr, но просто хотел удостовериться, что приведенный выше код является потокобезопасным.Доступ к `weak_ptr` и` shared_ptr` является атомарным

Я спрашиваю это, что если код выше действительно поточно, я не могу понять, как интерфейсы std::weak_ptr и std::shared_ptr сделать следующую операцию атомным expired() ? shared_ptr<T>() : shared_ptr<T>(*this). Мне просто кажется, что создание двух логических строк кода, подобных приведенным выше, невозможно сделать синхронным без использования какого-либо мьютекса или спин-блокировки.

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

+0

* "это очень похож на Потокобезопасную' shared_ptr '' * - Использует shared-'shared_ptr', чтобы построить 'weak_ptr' действительно поточно-безопасный? – Holt

+0

Нет ничего, что остановило бы реализацию от использования мьютекса, чтобы сделать 'lock' атомом, за исключением того, что он не будет очень эффективным. – Oktalist

ответ

2

Этот вопрос состоит из двух частей:

потокобезопасность

Код НЕ поточно, но это не имеет ничего общего с lock():
раса существует между int_ptr.reset(); и std::weak_ptr int_ptr_weak = int_ptr;.Потому что один поток модифицирует неатомную переменную int_ptr, а другой читает ее, которая по определению - гонка данных.

Так что это было бы хорошо:

int main() { 
    auto int_ptr = std::make_shared<int>(1); 
    std::weak_ptr<int> int_ptr_weak = int_ptr; //create the weak pointer in the original thread 
    std::thread th([&]() { 
     auto int_ptr_local = int_ptr_weak.lock(); 
     if (int_ptr_local) { 
      std::cout << "Value in the shared_ptr is " << *int_ptr_local << std::endl; 
     } 
    }); 

    int_ptr.reset(); 
    th.join(); 
} 

Atomic версия кода примера expired() ? shared_ptr<T>() : shared_ptr<T>(*this)

Конечно, весь процесс не может быть атомарным. Фактически важная часть состоит в том, что сильное количество ссылок только увеличивается, если оно уже больше нуля и что проверка и приращение происходят в атомном режиме. Я не знаю, есть ли система/архитектура конкретные примитивов, доступные для этого, но один из способов его реализации в C++ 11 будет:

std::shared_ptr<T> lock() { 
    if (!isInitialized) { 
     return std::shared_ptr<T>(); 
    } 
    std::atomic<int>& strong_ref_cnt = get_strong_ref_cnt_var_from_control_block(); 
    int old_cnt = strong_ref_cnt.load(); 
    while (old_cnt && !strong_ref_cnt.compare_exchange_weak(old_cnt, old_cnt + 1)) { 
     ; 
    } 
    if (old_cnt > 0) { 
     // create shared_ptr without touching the control block any further 
    } else { 
     // create empty shared_ptr 
    } 
} 
+0

Спасибо за ответ! Единственная часть, которую я смутил здесь, заключалась в том, что способ сделать «lock()» включает в себя блокировку (в вашем примере блокировка спина), я был в предположении, что стандарт сказал, что функция 'lock()' выполняться без блокировки. Разве это не так? – Curious

+0

@Curious: Откуда у вас создалось впечатление, что операция должна быть заблокирована? Стандарт только говорит, что это должно произойти атомарно, что не то же самое, что блокировка. Фактически, операции с 'std :: atomic_flag' являются единственными операциями, которые стандарт требует блокировки. Также я не уверен, если бы я назвал это спин-блокировкой. поскольку код не приобретает, освобождает или ждет чего-либо здесь – MikeMB

1

«как интерфейсы std::weak_ptr и std::shared_ptr сделать следующую операцию атомных expired() ? shared_ptr<T>() : shared_ptr<T>(*this)»

интерфейсы не. Это внутренняя реализация. Именно то, как это делается, будет отличаться от реализаций.

+0

Я просто не понял, как эта строка кода может быть атомарной без блокировок. Не могли бы вы привести пример любой такой реализации? – Curious

+1

@ Curious: Эта строка кода не является атомной. У реализации будет другой код. Поскольку это шаблон, вы можете просто найти пример в своей реализации. – MSalters

4

Является ли код над потоком безопасным?

Я считаю, что это не так, как int_ptr.reset(nullptr); мчится против std::weak_ptr int_ptr_weak = int_ptr;

Я не могу понять, как станд :: weak_ptr и станд :: shared_ptr интерфейсы сделать следующую операцию атомное expired() ? shared_ptr<T>() : shared_ptr<T>(*this)

Такая операция не является атомарной, так как expired() может возвращать значение false, но к тому моменту, когда вы действуете на это значение, это может быть более неточным. С другой стороны, если он возвращает true, это гарантирует, что он останется точным, если никто не модифицировал данный экземпляр shared_ptr. То есть операции с другими копиями данного shared_ptr не могут привести к его невыполнению.

Реализация weak_ptr::lock() не используется expired(). Вероятно, это что-то вроде атомного сравнения, где добавляется дополнительная сильная ссылка, только если текущее количество сильных ссылок больше нуля.

+0

Я спросил, потому что cppreference говорит, что строка кода является атомарной. http://en.cppreference.com/w/cpp/memory/weak_ptr/lock Я интерпретировал это неправильно? – Curious

+1

Текст на cppreference.com выглядит следующим образом: _Effectively возвращает expired()? shared_ptr (): shared_ptr (* this), выполненный atomically_. Это должно интерпретироваться как «делает что-то с этой точки зрения, но атомарно». –

+0

Но тогда код является потокобезопасным? – Curious

3

Нет, ваш код не является потокобезопасным. Существует гонка данных между операцией int_ptr.reset() в основном потоке (которая является операцией записи) и инициализацией int_weak_ptr от int_ptr в th (что является операцией чтения).

+0

Но cppreference, похоже, говорит, что метод 'lock' выполняет свою логику атомарно http://en.cppreference.com/w/cpp/memory/weak_ptr/lock – Curious

+2

@Curious В ответе не упоминается' lock'. – Oktalist

 Смежные вопросы

  • Нет связанных вопросов^_^