threads::shared
СОДЕРЖАНИЕ
- ИМЯ
- ВЕРСИЯ
- СИНОПСИС
- ОПИСАНИЕ
- ЭКСПОРТ
- ФУНКЦИИ
- ОБЪЕКТЫ
- ПРИМЕЧАНИЯ
- ПРЕДУПРЕЖДЕНИЯ
- ОШИБКИ И ОГРАНИЧЕНИЯ
- СМОТРИТЕ ТАКЖЕ
- АВТОР
- ЛИЦЕНЗИЯ
ИМЯ
threads::shared - Расширение Perl для совместного использования структур данных между потоками
ВЕРСИЯ
В данном документе описывается версия threads::shared 1.68
СИНОПСИС
use threads;
use threads::shared;
my $var :shared;
my %hsh :shared;
my @ary :shared;
my ($scalar, @array, %hash);
share($scalar);
share(@array);
share(%hash);
$var = $scalar_value;
$var = $shared_ref_value;
$var = shared_clone($non_shared_ref_value);
$var = shared_clone({'foo' => [qw/foo bar baz/]});
$hsh{'foo'} = $scalar_value;
$hsh{'bar'} = $shared_ref_value;
$hsh{'baz'} = shared_clone($non_shared_ref_value);
$hsh{'quz'} = shared_clone([1..3]);
$ary[0] = $scalar_value;
$ary[1] = $shared_ref_value;
$ary[2] = shared_clone($non_shared_ref_value);
$ary[3] = shared_clone([ {}, [] ]);
{ lock(%hash); ... }
cond_wait($scalar);
cond_timedwait($scalar, time() + 30);
cond_broadcast(@array);
cond_signal(%hash);
my $lockvar :shared;
# condition var != lock var
cond_wait($var, $lockvar);
cond_timedwait($var, time()+30, $lockvar); ОПИСАНИЕ
По умолчанию переменные являются частными для каждого потока, и каждый новый созданный поток получает собственную копию каждой существующей переменной. Этот модуль позволяет вам использовать переменные совместно между различными потоками (и псевдо-вилками в Win32). Он используется вместе с модулем threads.
Этот модуль поддерживает совместное использование только следующих типов данных: скаляры и ссылки на скаляры, массивы и ссылки на массивы, и хэши и ссылки на хэши.
ЭКСПОРТ
Следующие функции экспортируются этим модулем: share, shared_clone, is_shared, cond_wait, cond_timedwait, cond_signal и cond_broadcast.
Обратите внимание, что если этот модуль импортируется, когда модуль threads еще не загружен, все эти функции становятся пустыми операциями. Это позволяет создавать модули, которые будут работать как в многопоточных, так и в однопоточных средах.
ФУНКЦИИ
-
shareпринимает переменную и помечает её как общую:my ($scalar, @array, %hash); share($scalar); share(@array); share(%hash);shareвернёт общую ссылку, но всегда в виде ссылки.Переменные также могут быть помечены как общие во время компиляции с помощью атрибута
:shared.my ($var, %hash, @array) :shared;Общие переменные могут содержать только скаляры, ссылки на общие переменные или ссылки на общие данные (обсуждаются в следующем разделе):
my ($var, %hash, @array) :shared; my $bork; # Storing scalars $var = 1; $hash{'foo'} = 'bar'; $array[0] = 1.5; # Storing shared refs $var = \%hash; $hash{'ary'} = \@array; $array[1] = \$var; # The following are errors: # $var = \$bork; # ref of non-shared variable # $hash{'bork'} = []; # non-shared array ref # push(@array, { 'x' => 1 }); # non-shared hash ref -
shared_cloneпринимает ссылку и возвращает общую версию аргумента, выполняя глубокую копию всех элементов, которые не являются общими. Любые общие элементы в аргументе используются как есть (т.е. не клонируются).my $cpy = shared_clone({'foo' => [qw/foo bar baz/]});Статус объекта (т.е. класс, в который благословлен объект) также клонируется.
my $obj = {'foo' => [qw/foo bar baz/]}; bless($obj, 'Foo'); my $cpy = shared_clone($obj); print(ref($cpy), "\n"); # Outputs 'Foo'Для клонирования пустых ссылок на массивы или хэши также можно использовать:
$var = &share([]); # Same as $var = shared_clone([]); $var = &share({}); # Same as $var = shared_clone({});Не все типы данных Perl могут быть клонированы (например, глобы, ссылки на код). По умолчанию
shared_cloneбудет croak, если встретит такие элементы. Чтобы изменить это поведение на предупреждение, необходимо установить следующее:$threads::shared::clone_warn = 1;В этом случае
undefбудет заменено на элемент, подлежащий клонированию. Если установлено значение ноль:$threads::shared::clone_warn = 0;то замена
undefбудет выполнена молча. -
is_sharedпроверяет, является ли указанная переменная общей или нет. Если общая, возвращает внутренний идентификатор переменной (аналогичноrefaddr(), см. Scalar::Util). В противном случае возвращаетundef.if (is_shared($var)) { print("\$var is shared\n"); } else { print("\$var is not shared\n"); }При использовании с элементом массива или хэша,
is_sharedпроверяет, принадлежит ли указанный элемент общему массиву или хэшу. (Он не проверяет содержимое этого элемента.)my %hash :shared; if (is_shared(%hash)) { print("\%hash is shared\n"); } $hash{'elem'} = 1; if (is_shared($hash{'elem'})) { print("\$hash{'elem'} is in a shared hash\n"); } - lock VARIABLE
-
lockнакладывает консультативный замок на переменную до тех пор, пока замок не выйдет из области видимости. Если переменная заблокирована другим потоком, вызовlockбудет блокироваться до тех пор, пока она не станет доступной. Многократные вызовыlockодним и тем же потоком из динамически вложенных областей видимости безопасны — переменная останется заблокированной до тех пор, пока внешний замок на переменной не выйдет из области видимости.lockследует за ссылками ровно на один уровень:my %hash :shared; my $ref = \%hash; lock($ref); # This is equivalent to lock(%hash)Обратите внимание, что вы не можете явно разблокировать переменную; вы можете только дождаться, пока замок не выйдет из области видимости. Это наиболее просто достигается путем блокировки переменной внутри блока.
my $var :shared; { lock($var); # $var is locked from here to the end of the block ... } # $var is now unlockedПоскольку замки являются консультативными, они не препятствуют доступу или изменению данных другим потоком, который сам не пытается получить замок на переменную.
Вы не можете заблокировать отдельные элементы контейнерной переменной:
my %hash :shared; $hash{'foo'} = 'bar'; #lock($hash{'foo'}); # Error lock(%hash); # WorksЕсли вам нужен более тонкий контроль над доступом к общей переменной, см. Thread::Semaphore.
- cond_wait VARIABLE
- cond_wait CONDVAR, LOCKVAR
-
Функция
cond_waitпринимает заблокированную переменную в качестве параметра, разблокирует переменную и блокируется до тех пор, пока другой поток не выполнитcond_signalилиcond_broadcastдля той же заблокированной переменной. Переменная, на которойcond_waitбыл заблокирован, снова блокируется после того, какcond_waitбудет выполнен. Если несколько потоковcond_waitна одной и той же переменной, все, кроме одного, будут снова заблокированы, ожидая повторного захвата замка на переменной. (Поэтому, если вы используетеcond_waitтолько для синхронизации, откажитесь от замка как можно быстрее).Во втором формате
cond_waitпринимает общую, разблокированную переменную, за которой следует общая, заблокированная переменная. Вторая переменная разблокируется, а выполнение потока приостанавливается до тех пор, пока другой поток не отправит сигнал на первую переменную.Важно отметить, что на переменную можно получить уведомление даже если ни один поток
cond_signalилиcond_broadcastна переменной. Поэтому важно проверить значение переменной и вернуться к ожиданию, если требование не выполнено. Например, чтобы приостановить выполнение, пока общий счётчик не станет равным нулю:{ lock($counter); cond_wait($counter) until $counter == 0; } - cond_timedwait VARIABLE, ABS_TIMEOUT
- cond_timedwait CONDVAR, ABS_TIMEOUT, LOCKVAR
-
В формате с двумя аргументами
cond_timedwaitпринимает заблокированную переменную и абсолютный таймаут в секундах эпохи (см. time() в perlfunc для получения дополнительной информации) в качестве параметров, разблокирует переменную и блокируется до тех пор, пока таймаут не истечёт или другой поток не отправит сигнал на переменную. Значение false возвращается, если таймаут истек, и значение true в противном случае. В любом случае переменная повторно блокируется по возвращении.Подобно
cond_wait, эта функция может принять общую, заблокированную переменную в качестве дополнительного параметра; в этом случае первый параметр — это разблокированная переменная состояния, защищенная отдельной переменной блокировки.Опять же, как и в
cond_wait, пробуждение и повторный захват замка не являются атомарными, и вы всегда должны проверять своё желаемое состояние после возвращения из этой функции. Однако, поскольку таймаут является абсолютной величиной, его не нужно пересчитывать с каждым проходом:lock($var); my $abs = time() + 15; until ($ok = desired_condition($var)) { last if !cond_timedwait($var, $abs); } # we got it if $ok, otherwise we timed out! - cond_signal VARIABLE
-
Функция
cond_signalпринимает заблокированную переменную в качестве параметра и разблокирует один поток, который находится вcond_waitна этой переменной. Если вcond_waitна этой переменной заблокировано более одного потока, только один (и какой именно — непредсказуемо) будет разблокирован.Если ни один поток не заблокирован в
cond_waitна переменной, сигнал игнорируется. Блокируя перед отправкой сигнала, вы можете (с осторожностью) избежать отправки сигнала, прежде чем другой поток войдёт в cond_wait().cond_signalобычно генерирует предупреждение, если вы пытаетесь использовать его на разблокированной переменной. В тех редких случаях, когда это может быть целесообразно, вы можете подавить предупреждение с помощью:{ no warnings 'threads'; cond_signal($foo); } - cond_broadcast VARIABLE
-
Функция
cond_broadcastработает аналогичноcond_signal.cond_broadcast, однако, разблокирует все потоки, заблокированные вcond_waitна заблокированной переменной, а не только один.
ОБЪЕКТЫ
threads::shared экспортирует версию bless(), которая работает с общими объектами таким образом, что благословения распространяются через потоки.
# Create a shared 'Foo' object
my $foo :shared = shared_clone({});
bless($foo, 'Foo');
# Create a shared 'Bar' object
my $bar :shared = shared_clone({});
bless($bar, 'Bar');
# Put 'bar' inside 'foo'
$foo->{'bar'} = $bar;
# Rebless the objects via a thread
threads->create(sub {
# Rebless the outer object
bless($foo, 'Yin');
# Cannot directly rebless the inner object
#bless($foo->{'bar'}, 'Yang');
# Retrieve and rebless the inner object
my $obj = $foo->{'bar'};
bless($obj, 'Yang');
$foo->{'bar'} = $obj;
})->join();
print(ref($foo), "\n"); # Prints 'Yin'
print(ref($foo->{'bar'}), "\n"); # Prints 'Yang'
print(ref($bar), "\n"); # Also prints 'Yang' ПРИМЕЧАНИЯ
threads::shared разработан так, чтобы молча отключаться, если потоки недоступны. Это позволяет вам создавать модули и пакеты, которые могут быть использованы как в многопоточных, так и в однопоточных приложениях.
Если вам нужен доступ к потокам, вы должны use threads до того, как use threads::shared. threads выведет предупреждение, если вы используете его после threads::shared.
ПРЕДУПРЕЖДЕНИЯ
- cond_broadcast() вызвана для разблокированной переменной
- cond_signal() вызвана для разблокированной переменной
-
См. "cond_signal VARIABLE", выше.
ОШИБКИ И ОГРАНИЧЕНИЯ
Когда share используется с массивами, хэшами, ссылками на массивы или ссылки на хэши, все данные в них будут потеряны.
my @arr = qw(foo bar baz);
share(@arr);
# @arr is now empty (i.e., == ());
# Create a 'foo' object
my $foo = { 'data' => 99 };
bless($foo, 'foo');
# Share the object
share($foo); # Contents are now wiped out
print("ERROR: \$foo is empty\n")
if (! exists($foo->{'data'})); Поэтому заполняйте такие переменные после объявления их общими. (Скаляры и ссылки на скаляры не затронуты этой проблемой.)
Благословение общего элемента после его вложения в другой общий элемент не распространяет благословение на общую ссылку:
my $foo = &share({});
my $bar = &share({});
$bar->{foo} = $foo;
bless($foo, 'baz'); # $foo is now of class 'baz',
# but $bar->{foo} is unblessed. Поэтому вы должны благословить объекты перед их совместным использованием.
Часто не рекомендуется использовать общий объект, если сам класс не написан для поддержки совместного использования. Например, деструктор общего объекта может быть вызван многократно, один раз для выхода из области видимости каждого потока, или может вообще не быть вызван, если он вложен в другой общий объект. Другая проблема заключается в том, что содержимое хэшей-объектов будет потеряно из-за вышеупомянутого ограничения. См. examples/class.pl (в дистрибутиве CPAN этого модуля) для того, как создать класс, поддерживающий совместное использование объектов.
Деструкторы могут не вызываться для объектов, если эти объекты всё ещё существуют во время глобального уничтожения. Если деструкторы должны вызываться, убедитесь, что нет циклических ссылок и что ничего не ссылается на объекты до завершения программы.
Не поддерживает splice для массивов. Не поддерживает явное изменение длины массива через $#array — используйте push и pop вместо этого.
Создание ссылок на элементы общих массивов и хэшей не автовивифицирует элементы, и аналогично, срез общего массива/хэша по несуществующим индексам/ключам не автовивифицирует элементы.
share() позволяет вам share($hashref->{key}) и share($arrayref->[idx]) без вывода сообщений об ошибках. Но $hashref->{key} или $arrayref->[idx] не разделяются, что приводит к ошибке "блокировка может быть использована только для общих значений" при попытке lock($hashref->{key}) или lock($arrayref->[idx]) в другом потоке.
Использование refaddr() ненадёжно для проверки эквивалентности двух общих ссылок (например, при проверке на наличие циклических ссылок). Используйте is_shared() вместо этого:
use threads;
use threads::shared;
use Scalar::Util qw(refaddr);
# If ref is shared, use threads::shared's internal ID.
# Otherwise, use refaddr().
my $addr1 = is_shared($ref1) || refaddr($ref1);
my $addr2 = is_shared($ref2) || refaddr($ref2);
if ($addr1 == $addr2) {
# The refs are equivalent
} each() не работает должным образом с общими ссылками, встроенными в общие структуры. Например:
my %foo :shared;
$foo{'bar'} = shared_clone({'a'=>'x', 'b'=>'y', 'c'=>'z'});
while (my ($key, $val) = each(%{$foo{'bar'}})) {
...
} Вместо этого можно использовать любой из следующих вариантов:
my $ref = $foo{'bar'};
while (my ($key, $val) = each(%{$ref})) {
...
}
foreach my $key (keys(%{$foo{'bar'}})) {
my $val = $foo{'bar'}{$key};
...
} Этот модуль поддерживает переменные с двойным значением, созданные с помощью dualvar() из Scalar::Util. Однако, хотя $! ведет себя как dualvar, он реализован как привязанный SV. Для распространения его значения, при необходимости, используйте следующий конструкт:
my $errno :shared = dualvar($!,$!); Просматривайте существующие сообщения об ошибках по адресу и отправляйте любые новые ошибки, проблемы, исправления и т. д. по адресу: http://rt.cpan.org/Public/Dist/Display.html?Name=threads-shared
СМОТРИТЕ ТАКЖЕ
threads::shared на MetaCPAN: https://metacpan.org/release/threads-shared
Репозиторий кода для дистрибутива CPAN: https://github.com/Dual-Life/threads-shared
http://www.perl.com/pub/a/2002/06/11/threads.html и http://www.perl.com/pub/a/2002/09/04/threads.html
Список рассылки по Perl threads: http://lists.perl.org/list/ithreads.html
Пример кода в каталоге examples этого дистрибутива на CPAN.
АВТОР
Artur Bergman <sky AT crucially DOT net>
Документация позаимствована из старой Thread.pm.
Версия CPAN, подготовленная Jerry D. Hedden <jdhedden AT cpan DOT org>.
ЛИЦЕНЗИЯ
threads::shared распространяется по той же лицензии, что и Perl.
© 1993–2023 Larry Wall and others
Licensed under the GNU General Public License version 1 or later, or the Artistic License.
The Perl logo is a trademark of the Perl Foundation.
https://perldoc.perl.org/5.38.0/threads::shared