В C++ std::shared_ptr — это интеллектуальный указатель, который позволяет нескольким объектам совместно владеть одним ресурсом. Это может быть полезно для таких вещей, как кэширование или общие структуры данных. Когда создается новый shared_ptr, он указывает на динамически размещаемый объект и устанавливает счетчик ссылок равным 1. Всякий раз, когда создается другой shared_ptr, указывающий на тот же объект, его счетчик ссылок увеличивается на 1. Когда shared_ptr выходит за пределы области действия или сбрасывается, его счетчик ссылок уменьшается на 1. Когда счетчик ссылок достигает 0, управляемый объект удаляется. std::shared_ptr является потокобезопасным в том смысле, что к нему могут безопасно обращаться несколько потоков одновременно. Счетчик ссылок, используемый shared_ptr для отслеживания количества ссылок на управляемый объект, является атомарным, что означает, что он может безопасно обновляться несколькими потоками одновременно, не вызывая состояния гонки или неопределенного поведения.
Однако, если несколько потоков обращаются к одному и тому же объекту std::shared_ptr без надлежащей синхронизации, действительно существует риск повреждения данных или других ошибок. Чтобы обеспечить безопасный одновременный доступ к общему ресурсу, необходимо использовать надлежащие механизмы синхронизации, такие как мьютексы или атомарные операции. В противном случае при неправильной синхронизации одновременный доступ к одному и тому же объекту std::shared_ptr может привести к состязаниям и повреждению данных.

Чтобы проиллюстрировать это, рассмотрим следующий код, который создает 10 потоков, каждый из которых вызывает функцию thread_fcn(). Функция thread_fcn() увеличивает значение объекта global_instance std::shared_ptr в 10 000 раз. Затем функция main() приостанавливается на 5000 миллисекунд, что дает потокам время для выполнения. После завершения выполнения потоков функция main() выводит значение объекта global_instance std::shared_ptr.

#include <iostream>
#include <memory>
#include <thread>
#include <chrono>
#include <mutex>
#include <vector>
std::shared_ptr<int> global_instance = std::make_shared<int>(0);
std::mutex m;
constexpr int max_loop = 10000;
void thread_fcn()
{
  // thread-safe reference counting 
  for (int i = 0; i < max_loop; i++)
  {
    std::shared_ptr<int> temp = global_instance;
    *temp = *temp + 1;
  }
  
   std::cout << "global_instance use count : " << global_instance.use_count() << std::endl;
}

int main()
{
 *global_instance = 0;
std::vector<std::thread> threadList;
 for (int i = 0; i < 10; ++i)
 {
 threadList.push_back(std::thread(thread_fcn));
 }
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
 for (auto & thread : threadList)
 {
 thread.join();
 }
std::cout << __FUNCTION__ << "-> global_instance : " << *global_instance << std::endl;
 return 0;
}
main-> global_instance : 27319

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

 std::cout << "global_instance use count : " << global_instance.use_count() << std::endl;

Shared_ptr гарантирует, что подсчет ссылок для объекта, на который он указывает, является потокобезопасным, что означает, что несколько потоков могут получать доступ и изменять счетчик, не вызывая состояния гонки. Следовательно, если вы выведете сообщение на консоль, счетчик использования будет отличаться от 1 для каждого потока, когда мьютекс не используется. Есть 10 потоков, консольный вывод запишет 10 вывода.

global_instance use count : 4
global_instance use count : 2
global_instance use count : 4
global_instance use count : 5
global_instance use count : 5
global_instance use count : 3
global_instance use count : 3
global_instance use count : 1
global_instance use count : 3
global_instance use count : 2
main-> global_instance : 58398

Чтобы обеспечить безопасность потоков при увеличении общего целочисленного объекта, мы можем использовать мьютекс. Рассмотрим следующий код, который создает общий указатель на целочисленный объект, а затем создает 10 потоков. Каждый поток вызывает функцию thread_fcn_thread_safe(), которая увеличивает значение целочисленного объекта с помощью мьютекса для обеспечения безопасности потока. Через 5 секунд основной поток объединяет все потоки, а затем печатает значение целочисленного объекта.

#include <iostream>
#include <memory>
#include <thread>
#include <chrono>
#include <mutex>
#include <vector>
std::shared_ptr<int> global_instance = std::make_shared<int>(0);
std::mutex m;
constexpr int max_loop = 10000;
void thread_fcn_thread_safe()
{
 std::lock_guard<std::mutex> lock(m);
    for (int i = 0; i < max_loop; i++)
    {
        std::shared_ptr<int> temp = global_instance;
        *temp = *temp + 1;
    }

   std::cout << "global_instance use count : " << global_instance.use_count() << std::endl;
}
int main()
{
 *global_instance = 0;
 std::vector<std::thread> threadList;
for (int i = 0; i < 10; ++i)
 {
 threadList.push_back(std::thread(thread_fcn_thread_safe));
 }
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
 for (auto & thread : threadList)
 {
 thread.join();
 }
std::cout << __FUNCTION__ << "-> global_instance : " << *global_instance << std::endl;
 return 0;
}
main-> global_instance : 100000

Как и ожидалось, выход этой поточно-ориентированной реализации всегда равен 10 000. Это связано с тем, что каждый поток мог увеличивать значение целочисленного объекта без какого-либо вмешательства со стороны других потоков.

Поскольку мьютекс не позволяет другому потоку получать ресурсы для изменения, счетчик использования всегда будет равен 1, как показано ниже.

global_instance use count : 1
global_instance use count : 1
global_instance use count : 1
global_instance use count : 1
global_instance use count : 1
global_instance use count : 1
global_instance use count : 1
global_instance use count : 1
global_instance use count : 1
global_instance use count : 1
main-> global_instance : 100000

В C++ std::shared_ptr — это мощный инструмент, который можно использовать для управления общим владением ресурсами. Однако важно помнить, что std::shared_ptr сам по себе не является потокобезопасным. Чтобы обеспечить безопасность потоков, вам необходимо использовать его в сочетании с примитивом синхронизации, таким как мьютекс.