multiprocessing dengan python - apa yang diwarisi oleh proses forkserver dari proses induk?

Aug 15 2020

Saya mencoba menggunakan forkserverdan saya temui NameError: name 'xxx' is not defineddalam proses pekerja.

Saya menggunakan Python 3.6.4, tetapi dokumentasinya harus sama, dari https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods dikatakan bahwa:

Proses server garpu adalah utas tunggal sehingga aman untuk menggunakan os.fork (). Tidak ada sumber daya yang tidak perlu diwariskan.

Juga, dikatakan:

Lebih baik mewarisi dari pada acar / tidak diikat

Saat menggunakan metode spawn atau forkserver start, banyak jenis dari multiprocessing harus dapat dipilih sehingga proses anak dapat menggunakannya . Namun, orang biasanya harus menghindari pengiriman objek bersama ke proses lain menggunakan pipa atau antrian. Sebaliknya Anda harus mengatur program sehingga proses yang membutuhkan akses ke sumber daya bersama yang dibuat di tempat lain dapat mewarisinya dari proses leluhur.

Jadi ternyata objek kunci yang perlu dikerjakan oleh proses pekerja saya tidak diwarisi oleh proses server dan kemudian diteruskan ke pekerja, mengapa itu terjadi? Saya bertanya-tanya apa sebenarnya yang diwarisi oleh proses forkserver dari proses induk?

Inilah tampilan kode saya:

import multiprocessing
import (a bunch of other modules)

def worker_func(nameList):
    global largeObject
    for item in nameList:
        # get some info from largeObject using item as index
        # do some calculation
        return [item, info]

if __name__ == '__main__':
    result = []
    largeObject # This is my large object, it's read-only and no modification will be made to it.
    nameList # Here is a list variable that I will need to get info for each item in it from the largeObject    
    ctx_in_main = multiprocessing.get_context('forkserver')
    print('Start parallel, using forking/spawning/?:', ctx_in_main.get_context())
    cores = ctx_in_main.cpu_count()
    with ctx_in_main.Pool(processes=4) as pool:
        for x in pool.imap_unordered(worker_func, nameList):
            result.append(x)

Terima kasih!

Terbaik,

Jawaban

1 alex_noname Aug 16 2020 at 19:32

Teori

Di bawah ini adalah kutipan dari blog Bojan Nikolic

Versi Python modern (di Linux) menyediakan tiga cara untuk memulai proses terpisah:

  1. Fork () -ing proses induk dan melanjutkan dengan gambar proses yang sama di induk dan anak. Metode ini cepat, tetapi berpotensi tidak dapat diandalkan jika status induknya rumit

  2. Memunculkan proses anak, yaitu fork () - ing dan kemudian execv untuk mengganti image proses dengan proses Python baru. Metode ini dapat diandalkan tetapi lambat, karena gambar proses dimuat ulang lagi.

  3. Mekanisme forkserver , yang terdiri dari server Python terpisah dengan yang memiliki status yang relatif sederhana dan yang bercabang () - ed ketika diperlukan proses baru. Metode ini menggabungkan kecepatan Fork () - ing dengan keandalan yang baik (karena induk yang di-fork dalam keadaan sederhana).

Forkserver

Metode ketiga, forkserver , diilustrasikan di bawah ini. Perhatikan bahwa anak-anak menyimpan salinan status forkserver. Status ini dimaksudkan agar relatif sederhana, tetapi dimungkinkan untuk menyesuaikan ini melalui multiproses API melalui set_forkserver_preload()metode.

Praktek

Jadi, jika Anda ingin simething diwarisi oleh proses anak dari induk, ini harus ditentukan dalam status forkserver dengan cara set_forkserver_preload(modules_names), yang mengatur daftar nama modul untuk mencoba memuat dalam proses forkserver. Saya berikan contoh di bawah ini:

# inherited.py
large_obj = {"one": 1, "two": 2, "three": 3}
# main.py
import multiprocessing
import os
from time import sleep

from inherited import large_obj


def worker_func(key: str):
    print(os.getpid(), id(large_obj))
    sleep(1)
    return large_obj[key]


if __name__ == '__main__':
    result = []
    ctx_in_main = multiprocessing.get_context('forkserver')
    ctx_in_main.set_forkserver_preload(['inherited'])
    cores = ctx_in_main.cpu_count()
    with ctx_in_main.Pool(processes=cores) as pool:
        for x in pool.imap(worker_func, ["one", "two", "three"]):
            result.append(x)
    for res in result:
        print(res)

Keluaran:

# The PIDs are different but the address is always the same
PID=18603, obj id=139913466185024
PID=18604, obj id=139913466185024
PID=18605, obj id=139913466185024

Dan jika kami tidak menggunakan preloading

...
    ctx_in_main = multiprocessing.get_context('forkserver')
    # ctx_in_main.set_forkserver_preload(['inherited']) 
    cores = ctx_in_main.cpu_count()
...
# The PIDs are different, the addresses are different too
# (but sometimes they can coincide)
PID=19046, obj id=140011789067776
PID=19047, obj id=140011789030976
PID=19048, obj id=140011789030912
1 sgyzetrov Aug 17 2020 at 03:00

Jadi setelah diskusi yang menginspirasi dengan Alex, saya pikir saya memiliki info yang cukup untuk menjawab pertanyaan saya: apa sebenarnya yang diwarisi oleh proses forkserver dari proses induk?

Pada dasarnya ketika proses server dimulai, itu akan mengimpor modul utama Anda dan semua yang sebelumnya if __name__ == '__main__'akan dijalankan. Itulah mengapa kode saya tidak berfungsi, karena large_objecttidak dapat ditemukan di mana pun dalam serverproses dan dalam semua proses pekerja yang bercabang dari serverproses tersebut .

Solusi Alex berfungsi karena large_objectsekarang diimpor ke proses utama dan server sehingga setiap pekerja yang bercabang dari server juga akan mendapatkannya large_object. Jika digabungkan dengan set_forkserver_preload(modules_names)semua pekerja bahkan mungkin akan mendapatkan yang sama large_object dari apa yang saya lihat. Alasan penggunaan forkserverdijelaskan secara eksplisit dalam dokumentasi Python dan di blog Bojan:

Ketika program dimulai dan memilih metode mulai forkserver, proses server dimulai. Sejak saat itu, setiap kali proses baru diperlukan, proses induk terhubung ke server dan memintanya untuk melakukan proses baru. Proses server garpu adalah utas tunggal sehingga aman untuk menggunakan os.fork (). Tidak ada sumber daya yang tidak perlu diwariskan .

Mekanisme forkserver, yang terdiri dari server Python terpisah dengan yang memiliki status yang relatif sederhana dan yang bercabang () - ed ketika diperlukan proses baru. Metode ini menggabungkan kecepatan Fork () - ing dengan keandalan yang baik (karena induk yang di-fork dalam keadaan sederhana) .

Jadi lebih pada sisi aman menjadi perhatian di sini.

Di samping catatan, jika Anda menggunakan forksebagai metode awal, Anda tidak perlu mengimpor apa pun karena semua proses anak mendapat salinan memori proses orang tua (atau referensi jika sistem menggunakan COW- copy-on-write, tolong perbaiki saya jika saya salah). Dalam hal ini menggunakan global large_objectakan membuat Anda akses ke large_objectdalam worker_funcsecara langsung.

The forkserverkekuatan tidak menjadi pendekatan yang cocok untuk saya karena masalah saya hadapi adalah overhead memori. Semua operasi yang membuat saya large_objectmemakan banyak memori, jadi saya tidak ingin ada sumber daya yang tidak perlu dalam proses pekerja saya.

Jika saya memasukkan semua kalkulasi itu langsung ke dalam inherited.pyseperti yang disarankan Alex, itu akan dieksekusi dua kali (sekali ketika saya mengimpor modul di main dan sekali ketika server mengimpornya; bahkan mungkin lebih ketika proses pekerja lahir?), Ini cocok jika saya hanya menginginkan proses aman single-threaded yang dapat digunakan oleh pekerja. Tetapi karena saya mencoba membuat pekerja tidak mewarisi sumber daya yang tidak perlu dan hanya mendapatkan large_object, ini tidak akan berhasil. Dan menempatkan perhitungan tersebut di __main__dalam inherited.pytidak akan bekerja baik karena sekarang tidak ada proses akan mengeksekusi mereka, termasuk utama dan server.

Jadi, sebagai kesimpulan, jika tujuannya di sini adalah untuk membuat pekerja mewarisi sumber daya minimal, saya lebih baik memecah kode saya menjadi 2, lakukan calculation.pypertama, buat acar large_object, keluar dari penerjemah, dan mulai yang baru untuk memuat acar large_object. Lalu aku bisa jadi gila dengan forkatau forkserver.