Тег non_sync¶
В Ü большинство типов должны следовать правилу вложенной неизменяемости. Это означает, что имея неизменяемую ссылку на некоторое значение, нельзя изменить вложенные в него значения - поля, содержимое контейнеров и т. д. Данное свойство полезно в многопоточной среде. Неизменяемые ссылки на значения подобных типов можно передать в разные потоки и не будет каких-либо проблем с синхронизацией.
Однако, существуют случаи, когда внутренняя изменяемость всё же бывает необходима.
Например, для типов вроде разделяемых указателей, где надо как минимум модифицировать счётчик ссылок.
При этом, такие типы уже небезпасно использовать в многопоточной среде.
Для того, чтобы предотвратить использование таких типов в многопоточной среде, в Ü существует тег non_sync
.
Данный тег можно добавить при объявлении класса.
При этом существуют два способа его объявить - безусловно, или при истинности условия в ()
после ключевого слова non_sync
.
Узнать же наличие non_sync
тега у типа можно с помощью non_sync
выражения.
При этом тег non_sync
распространяется рекурсивно.
Если хоть одно поле в классе имеет non_sync
тип, весь класс будет non_sync
.
non_sync
типами также являются массивы и кортежи, содержащие non_sync
типы.
class A non_sync {} // Безусловно non_sync
struct B non_sync( true ) {} // non_sync по условию
class C non_sync( false ) {} // не-non_sync по условию
class D{ A a; } // non_sync, т. к. содержит non_sync элементы
class E polymorph non_sync {}
class F : E { } // non_sync, т. к. имеет non_sync предка
// Фундаментальные типы не являются non_sync
static_assert( !non_sync</i32/> );
static_assert( !non_sync</bool/> );
static_assert( !non_sync</[f32, 4]/> );
static_assert( non_sync</A/> );
static_assert( non_sync</[A, 8]/> ); // Массив non_sync элементов является non_sync
static_assert( non_sync</B/> );
static_assert( non_sync</tup[bool, B, char8]/> ); // Кортеж с хотя бы одним non_sync элементом является non_sync
static_assert( !non_sync</C/> );
static_assert( !non_sync</[C, 7]/> );
static_assert( non_sync</D/> );
static_assert( non_sync</E/> );
static_assert( non_sync</F/> );
При объявлении non_sync
тега условным возможно при этом создать цикл зависимостей.
Это не страшно, пока выражение в условном non_sync
теге является non_sync
выражением.
class A non_sync( non_sync</A/> ) {} // самозависимость - результат будет не non_sync
static_assert( !non_sync</A/> );
class B non_sync( non_sync</C/> ) {} // циклическая зависимость - результат не будет non_sync, т. к. нету источника начального non_sync тега
class C non_sync( non_sync</B/> ) {}
static_assert( !non_sync</B/> );
static_assert( !non_sync</C/> );
class D non_sync( non_sync</E/> ) {} // циклическая зависимость через поле - результат будет non_sync, т. к. есть источник начального non_sync тега
class E non_sync { D d; }
static_assert( non_sync</D/> );
static_assert( non_sync</E/> );
Свйоство non_sync
нельзя изменять при наследовании.
Если non_sync
тип имеет не-non_sync
предка, компилятор выдаст ошибку компиляции.
Это необходимо, чтобы не терялось наличие non_sync
свойства при сохранении экземпляра производного класса через ссылку или контейнер для базового класса.
Использование тега non_sync¶
В обычном пользовательском коде тег non_sync
использовать нету нужды, т. к. без unsafe
сделать что-то non_sync
не выйдет.
Использовать тег non_sync
нужно только непосредственно в контейнерах, которые при помощи unsafe
кода реализуют некоторую потоконебезопасную внутреннюю изменяемость.
Кроме того, в контейнерах с непрямым хранением значений (box, vector, variant и т. д.) надо использовать условный тег non_sync
, зависящий от хранимого типа, дабы свойство non_sync
рекурсивно распространялось через эти контейнеры.
В коде, который каким-либо образом создаёт потоки, или в коде, где какой-либо объект передаётся в другой поток, следует добавлять static_assert( !non_sync</T/> )
, чтобы предотвратить использование небезопасных для многопоточного доступа типов.
Безопасная многопоточная изменяемость¶
Как было отмечено выше, значения типов, являющихся non_sync
нельзя использовать в многопоточной среде.
Но что, если всё же нужна внутренняя изменяемость совместно с многопоточностью?
Решение - использовать типы, реализующие потокобезопасную внутреннюю изменяемость.
Эти типы не помечены как non_sync
.
Внутри они используют какие-либо примитивы синхронизации, чтобы гарантировать отсутствие гонок.
Примеры таких примитивов - mutex, rw_lock, атомарные переменные и т. д.
При этом типы контейнеров с синхронизацией внутренней изменяемости должны проверять хранимый тип на отсутствие внутренней изменяемости - через static_assert( !non_sync</T/> )
.