Использование библиотечных функций в многопоточном коде (Common Lisp) - PullRequest
1 голос
/ 03 мая 2019

Когда переменная может быть доступна / обновлена ​​из нескольких потоков, она обычно нуждается в защите от одновременных изменений. Одним из эффективных подходов является использование атомарных функций для обеспечения взаимоисключающего доступа; например, (sb-ext:atomic-incf *count*). Другой подход заключается в том, чтобы обернуть блокировку вокруг операций обновления, например, (bt:with-lock-held (*lock*) (incf *count*)), но это несколько дорого.

Есть ли эффективный способ включить библиотечные функции (скажем, из библиотеки alexandria) в многопоточный код? Например, если вы хотите сделать (alexandria:deletef x *list*) из нескольких потоков? Или вам нужно сделать замок? (пс: я предполагаю, что deletef потребуется защита, но не совсем уверен.)

Ответы [ 2 ]

5 голосов
/ 04 мая 2019

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

Самым простым решением, как правило, является защита одновременных изменений с помощью блокировки, как в приведенном вами примере: (bt:with-lock-held (*lock*) (incf *count*)).Производительность приемлема в большинстве случаев.Я бы рассмотрел только другие варианты ниже, если вы сравните ваш конкретный вариант использования и обнаружите, что он слишком медленный для ваших нужд.

Атомарные операции, поскольку (sb-ext:atomic-incf *count*) - это примитив очень низкого уровня: очень быстрый, но очень сложно правильно составить в более сложные операции.Если нужные вам функции могут быть сопоставлены один за другим с атомарными операциями, вы можете просто использовать их, и все готово.Но в большинстве случаев вам необходимо составлять элементарные операции для обеспечения более сложных функций - и возникает трудность: вам необходимо глубоко понять архитектуру, которую вы используете, включая (отсутствие) гарантий упорядочения памяти и барьеров памяти.И это чрезвычайно сложный путь.

Моя библиотека STMX предоставляет предположительно интуитивно понятные и простые в составлении примитивы, например, (stmx:atomic (incf *count*)).Он внутренне использует атомарные операции (если доступны) и инструкции ЦП Intel TSX для транзакционной памяти (только в sbcl x86-64 и только в процессорах Intel, в которых они есть) для оптимизации скорости выполнения.

Он имеет несколько предостережений:

  1. он работает только для типов с поддержкой транзакций, таких как tvar, tcell, tcons, tlist, tmap, tfifo, tchannel, определены классыс (stmx:transactional (defclass ...)) или структурами, определенными с помощью (stmx:transactional (defstruct ...))

  2. код внутри (stmx:atomic ...) может быть повторен несколько раз в случае конфликтов между несколькими потоками, поэтому он не должен выполнять ввод-вывод.

  3. обычно он медленнее, чем оптимизированный вручную код, использующий атомарные операции, но его гораздо проще писать, в том числе потому, что он компонуется: (atomic (atomic (foo) (atomic (bar)) - это одна транзакция (не три)и эквивалентно (atomic (foo) (bar)).

В вашем конкретном случае вы хотите использовать существующие не поточно-ориентированные вызовы библиотеки как (alexandria:deletef x *list*) в многопоточном коде, т.е.сделать их потокобезопасными.

Если библиотеки делаютЕсли вы не используете и не изменяете глобальные переменные внутри, тогда блокировки и атомарные операции могут быть успешно использованы.Вместо этого STMX можно использовать только в том случае, если вы одновременно изменяете типы с поддержкой транзакций only - могут использоваться предоставляемые библиотекой типы, но они должны рассматриваться как доступные только для чтения параллельным кодом.

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

PS пожалуйста, отправляйте сообщения о проблемах STMX на https://github.com/cosmos72/stmx/issues @Svante, только что обновленный https://github.com/cosmos72/stmx/issues/14 с ошибкой @davipough в комментариях выше, но точные версии необходимы для устранения проблемы.

1 голос
/ 03 мая 2019

Вы можете использовать STMX для получения программных транзакций с «оптимистической блокировкой».

Это работает с классами, помеченными как транзакционные, или с транзакционными примитивами, также предоставляемыми библиотекой: tcell, tcons и т. Д. Вам необходимо использовать их или включать в них другие объекты. Места в этих структурах доступны механизму мест, поэтому такие библиотечные функции, как alexandria:deletef, просто работают.

...