Все зависит от того, как multiprocessing
реализовал RLock
. Я знаю, что многопроцессорная обработка может работать через хосты, что подразумевает, что примитивы синхронизации могут работать через сокеты. Если это так, это приведет к множеству (переменной) латентности.
Итак, я сделал эксперимент.
Вот простак пример RLock
используется более чем одним процессом (для предотвращения любой фаст-путь, где все замки находятся в пределах того же процесса):
#!/usr/bin/env python
import multiprocessing
from time import sleep
lock = multiprocessing.RLock()
def noop(myname):
# nonlocal lock
sleep(0.5)
print myname, "acquiring lock"
with lock:
print myname, "has lock"
sleep(0.5)
print myname, "released lock"
sProc1 = multiprocessing.Process(target=noop, args=('alice',))
sProc2 = multiprocessing.Process(target=noop, args=('bob',))
sProc1.start()
sProc2.start()
sProc1.join()
sProc2.join()
Когда это будет работать, его выход выглядит как это:
alice acquiring lock
alice has lock
bob acquiring lock
alice released lock
bob has lock
bob released lock
Великий, так что теперь запустить его с системным вызовом трассировку с помощью strace.
В приведенной ниже команде параметр -ff
сообщает инструменту «следовать за fork()
», т. Е. Отслеживать любые процессы, запущенные основным. По соображениям краткости я также использую -e trace=futex,write
, который фильтрует вывод на основе сделанных мной выводов, прежде чем публиковать это. Обычно вы запускаете без опции -e
и используете текстовый редактор/grep
, чтобы узнать, что произошло после факта.
# strace -ff -e trace=futex,write ./traceme.py
futex(0x7fffeafe29bc, FUTEX_WAIT_BITSET_PRIVATE|FUTEX_CLOCK_REALTIME, 1, NULL, 7fb92ac6c700) = -1 EAGAIN (Resource temporarily unavailable)
futex(0x7fb92a8540b0, FUTEX_WAKE_PRIVATE, 2147483647) = 0
futex(0x7fb92aa7131c, FUTEX_WAKE_PRIVATE, 2147483647) = 0
write(3, "\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 32) = 32
Process 25873 attached
Process 25874 attached
Process 25872 suspended
[pid 25873] write(1, "alice acquiring lock\n", 21alice acquiring lock
) = 21
[pid 25873] write(1, "alice has lock\n", 15alice has lock
) = 15
[pid 25874] write(1, "bob acquiring lock\n", 19bob acquiring lock
) = 19
[pid 25874] futex(0x7fb92ac91000, FUTEX_WAIT, 0, NULL <unfinished ...>
[pid 25873] futex(0x7fb92ac91000, FUTEX_WAKE, 1 <unfinished ...>
[pid 25874] <... futex resumed>) = 0
[pid 25873] <... futex resumed>) = 1
[pid 25874] write(1, "bob has lock\n", 13 <unfinished ...>
bob has lock
[pid 25873] write(1, "alice released lock\n", 20 <unfinished ...>
alice released lock
[pid 25874] <... write resumed>) = 13
[pid 25873] <... write resumed>) = 20
Process 25872 resumed
Process 25873 detached
[pid 25872] --- SIGCHLD (Child exited) @ 0 (0) ---
Process 25872 suspended
[pid 25874] write(1, "bob released lock\n", 18bob released lock
) = 18
Process 25872 resumed
Process 25874 detached
--- SIGCHLD (Child exited) @ 0 (0) ---
Из картины печати (write()
) сообщения и futex
вызовы, которые блокируют, а затем возобновить, представляется очевидным, что RLock
реализуется с помощью futex
или «Fast UserSpace мьютекс». Как следует из названия, это хороший выбор для синхронизации.
Когда процесс блокируется в системном вызове типа futex
, процесс блокирует ввод-вывод для всех целей и задач.
Все это означает, что multiprocessing.RLock
эффективен и выполняет то, что он предназначен для этого. Поэтому, если производительность вашего приложения меньше, чем вы ожидаете при использовании синхронизации, скорее всего, ваш алгоритм виноват.
Синхронизация между независимыми объектами (то есть процессами или хостами) требует, чтобы они продолжали спрашивать друг друга, если что-либо изменилось. У этого есть много тонких затрат, таких как обходные кеши, чтобы гарантировать, что все сущности видят одни и те же данные. Доступ к неиспользуемой памяти убивает производительность! – RobM
@RobM В приведенном примере используется один процесс, и нет конкуренции за ресурсы. – Apalala