Spec-Zone.ru › Perl 5.30

threads::shared

СОДЕРЖАНИЕ

  • ИМЯ
  • ВЕРСИЯ
  • СИНОПС
  • ОПИСАНИЕ
  • ЭКСПОРТ
  • ФУНКЦИИ
  • ОБЪЕКТЫ
  • ПРИМЕЧАНИЯ
  • ПРЕДУПРЕЖДЕНИЯ
  • ОШИБКИ И ОГРАНИЧЕНИЯ
  • СМОТРИТЕ ТАКЖЕ
  • АВТОР
  • ЛИЦЕНЗИЯ

ИМЯ

threads::shared — Perl-расширение для совместного использования структур данных между потоками

ВЕРСИЯ

В этом документе описана версия threads::shared 1.60

СИНОПС

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 VARIABLE

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 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 могут быть клонированы (например, globs, ссылки на код). По умолчанию, shared_clone будет выдавать ошибку если столкнётся с такими элементами. Чтобы изменить это поведение на предупреждение, необходимо задать следующее:

$threads::shared::clone_warn = 1;

В этом случае, undef будет заменён на элемент, который нужно клонировать. Если установлено значение ноль:

$threads::shared::clone_warn = 0;

то подстановка undef будет выполнена без предупреждений.

is_shared VARIABLE

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 для получения дополнительной информации) в качестве параметров, разблокирует переменную и блокирует выполнение, пока не истечёт лимит или другой поток не отправит сигнал переменной. Ложное значение возвращается, если истечёт лимит, и истинное значение в противном случае. В любом случае переменная повторно блокируется по возвращении.

Как и 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_signal обычно выдаёт предупреждение, если вы пытаетесь использовать его с разблокированной переменной. В редких случаях, когда это может быть разумно, вы можете подавить предупреждение с помощью:

{ no warnings 'threads'; cond_signal($foo); }
cond_broadcast VARIABLE

Функция cond_broadcast работает аналогично cond_signal. cond_broadcast, однако, разблокирует все потоки, которые ожидают заблокированной переменной, а не только один.

ОБЪЕКТЫ

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

threads, perlthrtut

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–2020 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.30.3/threads::shared

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API