Такие языки, как Swift, Vala и C ++ (через shared_ptr) управляют памятью путем подсчета ссылок. Насколько я знаю, обновления счетчика ссылок в этих системах выполняются атомарно и, таким образом, потокобезопасны.
Однако каждый раз, когда ссылка / указатель переназначается, для предыдущего объекта ссылки требуется уменьшение счетчика ссылок, для нового объекта ссылки увеличивается значение ссылки, и, наконец, сама ссылка должна быть переназначена. Таким образом, если одна и та же ссылка доступна из нескольких потоков (то есть через глобальную переменную) и переназначается несколькими потоками одновременно, подсчет ссылок может стать искаженным.
Так делаются ли общие указатели C ++, ссылки на Vala, ссылки Swift, чтобы избежать таких проблем? Если нет, то какие шаги необходимы на каждом из трех языков, чтобы сделать такой доступ безопасным?
Любые идеи приветствуются. Спасибо!
см. последний абзац http://en.cppreference.com/w/cpp/memory/shared_ptr
Все функции-члены (включая конструктор копирования и назначение копирования) могут быть вызваны несколькими потоками в разных экземплярах shared_ptr без дополнительной синхронизации, даже если эти экземпляры являются копиями и имеют общее владение одним и тем же объектом. Если несколько потоков выполнения обращаются к одному и тому же shared_ptr без синхронизации и любой из этих обращений использует неконстантную функцию-член shared_ptr, тогда произойдет гонка данных; перегрузки shared_ptr элементарных функций могут использоваться для предотвращения гонки данных.
shared_ptr
переменная не является потокобезопасным и не должен быть доступен из нескольких потоков, если один или несколько потоков изменяют переменная. Несколько переменных, управляющих одним и тем же указателем, являются атомарными, и каждый поток может модифицировать свою собственную копию shared_ptr
,
Например, это не безопасно:
#include <iostream>
#include <string>
#include <memory>
#include <vector>
#include <thread>
int main()
{
std::shared_ptr< std::string > str( new std::string() );
std::vector< std::thread > threads;
for ( int i = 0; i < 10; i++ )
{
threads.emplace_back([&]
{
if ( str->empty() )
{
str.reset( new std::string( "thread string" ) );
}
else
{
str.reset();
}
});
}
for ( auto& thread : threads )
{
thread.join();
}
}
но это как темы не изменяют str
переменная, но увеличить ее счетчик ссылок:
#include <iostream>
#include <string>
#include <memory>
#include <vector>
#include <thread>
int main()
{
std::shared_ptr< std::string > str( new std::string() );
std::vector< std::thread > threads;
for ( int i = 0; i < 10; i++ )
{
threads.emplace_back([&]
{
std::shared_ptr< std::string > str2 = str;
if ( str2->empty() )
{
str2.reset( new std::string( "thread string" ) );
}
else
{
str2.reset();
}
});
}
for ( auto& thread : threads )
{
thread.join();
}
}
C ++ 20 добавляет std::atomic_shared_ptr
что полностью потокобезопасно. До этого вы можете использовать атомарные функции, не являющиеся членами.
Подсчет ссылок является поточно-ориентированным в Swift, потому что основной NSObject потокобезопасен. В этом случае счетчик ссылок является неотъемлемым свойством самого объекта, поэтому ваш вопрос спорен. Похоже, то же самое относится и к Вала.
Что оставляет C ++, всегда опаздывает на бал.
std::shared_ptr
Реализация подсчет ссылок это потокобезопасно, как ясно показывает цитата из поста Алана, но указывать начать присматривать за другим объектом, очевидно, нет.
Это не принято делать это. Это, скорее, подрывает его назначение, конечно, если вы пытаетесь сделать его более безопасным для работы с кодом. Более подробная информация на cppreference — некоторые из этих перегрузок operator=
потокобезопасны, а некоторые нет.