Birden çok işleme tabi tutulan bir işlevin dönüş değerini nasıl kurtarabilirim?


190

Aşağıdaki örnek kodda, işlevin dönüş değerini kurtarmak istiyorum worker. Bunu nasıl yapabilirim? Bu değer nerede saklanır?

Örnek Kod:

import multiprocessing

def worker(procnum):
    '''worker function'''
    print str(procnum) + ' represent!'
    return procnum


if __name__ == '__main__':
    jobs = []
    for i in range(5):
        p = multiprocessing.Process(target=worker, args=(i,))
        jobs.append(p)
        p.start()

    for proc in jobs:
        proc.join()
    print jobs

Çıktı:

0 represent!
1 represent!
2 represent!
3 represent!
4 represent!
[<Process(Process-1, stopped)>, <Process(Process-2, stopped)>, <Process(Process-3, stopped)>, <Process(Process-4, stopped)>, <Process(Process-5, stopped)>]

Depolanan nesnelerde ilgili özniteliği bulamıyorum jobs.

Yanıtlar:


190

İletişim kurmak için paylaşılan değişkeni kullanın . Örneğin şöyle:

import multiprocessing

def worker(procnum, return_dict):
    '''worker function'''
    print str(procnum) + ' represent!'
    return_dict[procnum] = procnum


if __name__ == '__main__':
    manager = multiprocessing.Manager()
    return_dict = manager.dict()
    jobs = []
    for i in range(5):
        p = multiprocessing.Process(target=worker, args=(i,return_dict))
        jobs.append(p)
        p.start()

    for proc in jobs:
        proc.join()
    print return_dict.values()

46
Burada multiprocessing.Queuedeğil, yerine kullanmanızı tavsiye ederim Manager. Kullanmak Manageriçin tamamen yeni bir süreç ortaya çıkar Queue.
dano

1
@dano: Acaba, Queue () nesnesini kullanırsak, her işlem değeri döndürdüğünde siparişten emin olamayız. Yani sonuçta sıraya ihtiyacımız olursa, bir sonraki işi yapmak için. Hangi işlemin hangi süreçten tam olarak hangi çıktıdan geldiğinden nasıl emin olabiliriz
Catbuilts

4
@Catbuilts Her işlemden bir demet döndürebilirsiniz; burada bir değer, önem verdiğiniz gerçek dönüş değeri, diğeri ise işlemden benzersiz bir tanımlayıcıdır. Ama aynı zamanda neden hangi sürecin hangi değeri döndürdüğünü bilmeniz gerektiğini de merak ediyorum. Eğer süreç hakkında gerçekten bilmeniz gerekenler ise, ya da girdilerinizin listesi ile çıktıların listesi arasında bir ilişki kurmanız mı gerekiyor? Bu durumda, multiprocessing.Pool.mapiş öğeleri listenizi işlemek için kullanmanızı öneririm .
dano

5
yalnızca tek bir argümanla işlevler için uyarılar : kullanmalıdır args=(my_function_argument, ). Not ,burada virgül! Ya da Python “eksik konumsal argümanlar” dan şikayet eder. Anlamak için 10 dakika sürdü. Ayrıca manuel kullanımı da kontrol edin ("proses sınıfı" bölümünde).
yuqli

2
@vartec, bir multipriocessing.Manager () sözlüğü kullanmanın bir dezavantajı, döndürdüğü nesnenin turşular (serileştirilmesi) olmasıdır, bu nedenle nesnenin geri dönmesi için turşu kütüphanesi tarafından maksimum 2GiB boyutunda bir darboğaz vardır. Geri dönen nesnenin serileştirilmesinden kaçınmanın başka bir yolu var mı?
hirschme

68

@Sega_sai tarafından önerilen yaklaşım daha iyi olduğunu düşünüyorum. Ama gerçekten bir kod örneği gerekiyor, işte gidiyor:

import multiprocessing
from os import getpid

def worker(procnum):
    print('I am number %d in process %d' % (procnum, getpid()))
    return getpid()

if __name__ == '__main__':
    pool = multiprocessing.Pool(processes = 3)
    print(pool.map(worker, range(5)))

Hangi dönüş değerleri yazdırılacaktır:

I am number 0 in process 19139
I am number 1 in process 19138
I am number 2 in process 19140
I am number 3 in process 19139
I am number 4 in process 19140
[19139, 19138, 19140, 19139, 19140]

map(Python 2 yerleşik) hakkında bilginiz varsa, bu çok zor olmamalıdır. Aksi takdirde sega_Sai'nin bağlantısına bir göz atın .

Ne kadar az kod gerektiğini unutmayın. (İşlemlerin nasıl yeniden kullanıldığını da unutmayın).


1
Neden getpid()aynı değeri geri döndürdüğüm hakkında bir fikrin var mı? Python3 kullanıyorum
zelusp

Pool'un işçileri nasıl dağıtacağından emin değilim. Belki de gerçekten hızlılarsa hepsi aynı işçiyle sonuçlanabilir? Tutarlı bir şekilde mi oluyor? Ayrıca bir gecikme eklerseniz?
Mark

Ayrıca bunun hızla ilgili bir şey olduğunu düşündüm, ancak pool.map10.000'den fazla işlemi kullanarak 1.000.000 aralığı beslediğimde en fazla iki farklı pid görüyorum.
zelusp

1
O zaman emin değilim. Bunun için ayrı bir soru açmanın ilginç olacağını düşünüyorum.
Mark

Her bir sürece farklı bir işlev göndermek istediğiniz şeyler varsa şunu kullanın pool.apply_async: docs.python.org/3/library/…
Kyle

24

Bu örnek, çok işlemli bir listenin nasıl kullanılacağını gösterir. Rasgele sayıda işlemden dizeleri döndürmek için boru örnekleri:

import multiprocessing

def worker(procnum, send_end):
    '''worker function'''
    result = str(procnum) + ' represent!'
    print result
    send_end.send(result)

def main():
    jobs = []
    pipe_list = []
    for i in range(5):
        recv_end, send_end = multiprocessing.Pipe(False)
        p = multiprocessing.Process(target=worker, args=(i, send_end))
        jobs.append(p)
        pipe_list.append(recv_end)
        p.start()

    for proc in jobs:
        proc.join()
    result_list = [x.recv() for x in pipe_list]
    print result_list

if __name__ == '__main__':
    main()

Çıktı:

0 represent!
1 represent!
2 represent!
3 represent!
4 represent!
['0 represent!', '1 represent!', '2 represent!', '3 represent!', '4 represent!']

Bu çözüm, çok işlemciliğe göre daha az kaynak kullanır.

  • bir boru
  • en az bir Kilit
  • Bir tampon
  • bir iplik

veya multiprocessing.SimpleQueue olan kullanımları

  • bir boru
  • en az bir Kilit

Bu türlerin her birinin kaynağına bakmak çok öğreticidir.


Boruları küresel bir değişken yapmadan bunu yapmanın en iyi yolu ne olurdu?
Nickpick

Tüm küresel veri ve kodu bir ana fonksiyona koydum ve aynı şekilde çalışıyor. sorunuza cevap veriyor mu?
David Cullen

herhangi bir yeni değer eklenmeden (gönderilmeden) önce borunun her zaman okunması gerekir mi?
Nickpick

+1, iyi cevap. Ancak çözümün daha verimli olması konusunda, ödünleşim, Pipeher işlem Queueiçin bir işlem veya bir işlem yapmaktır . Bunun her durumda daha verimli olup olmadığını bilmiyorum.
sudo

2
Dönen nesne büyükse bu yanıt kilitlenmeye neden olur. Önce proc.join () yapmak yerine önce dönüş değerini recv () ve sonra birleştirme yapmaya çalışacağım.
L.Pes

22

Bazı nedenlerden dolayı, bunun Queueherhangi bir yerde nasıl yapılacağına dair genel bir örnek bulamadım (Python'un doktor örnekleri bile birden fazla işlem üretmiyor), bu yüzden 10 denemeden sonra çalıştığım şey:

def add_helper(queue, arg1, arg2): # the func called in child processes
    ret = arg1 + arg2
    queue.put(ret)

def multi_add(): # spawns child processes
    q = Queue()
    processes = []
    rets = []
    for _ in range(0, 100):
        p = Process(target=add_helper, args=(q, 1, 2))
        processes.append(p)
        p.start()
    for p in processes:
        ret = q.get() # will block
        rets.append(ret)
    for p in processes:
        p.join()
    return rets

Queuealt işlemlerden döndürülen değerleri depolamak için kullanabileceğiniz engelleme, iş parçacığı açısından güvenli bir kuyruktur. Bu yüzden kuyruğu her bir işleme geçirmek zorundasınız. Burada az belirgin şey yapmak zorunda olduğunu get()senden önce kuyruktan es ya da başka kuyruk doldurur ve bloklar her şeyi.joinProcess

Nesneye yönelik olanlar için güncelleme (Python 3.4'te test edilmiştir):

from multiprocessing import Process, Queue

class Multiprocessor():

    def __init__(self):
        self.processes = []
        self.queue = Queue()

    @staticmethod
    def _wrapper(func, queue, args, kwargs):
        ret = func(*args, **kwargs)
        queue.put(ret)

    def run(self, func, *args, **kwargs):
        args2 = [func, self.queue, args, kwargs]
        p = Process(target=self._wrapper, args=args2)
        self.processes.append(p)
        p.start()

    def wait(self):
        rets = []
        for p in self.processes:
            ret = self.queue.get()
            rets.append(ret)
        for p in self.processes:
            p.join()
        return rets

# tester
if __name__ == "__main__":
    mp = Multiprocessor()
    num_proc = 64
    for _ in range(num_proc): # queue up multiple tasks running `sum`
        mp.run(sum, [1, 2, 3, 4, 5])
    ret = mp.wait() # get all results
    print(ret)
    assert len(ret) == num_proc and all(r == 15 for r in ret)

18

Bir kullanımdan nasıl değer elde edeceğini arayan herkes Processiçin Queue:

import multiprocessing

ret = {'foo': False}

def worker(queue):
    ret = queue.get()
    ret['foo'] = True
    queue.put(ret)

if __name__ == '__main__':
    queue = multiprocessing.Queue()
    queue.put(ret)
    p = multiprocessing.Process(target=worker, args=(queue,))
    p.start()
    print queue.get()  # Prints {"foo": True}
    p.join()

1
işçi sürecimde bir sıraya koyduğumda birleştirmeye asla ulaşamıyorum. Bunun nasıl olabileceği hakkında bir fikrin var mı?
Laurens Koppenol

@LaurensKoppenol, ana kodunuzun p.join () 'de kalıcı olarak asılı kaldığını ve hiçbir zaman devam etmediğini mi söylüyorsunuz? İşleminizde sonsuz bir döngü var mı?
Matthew Moisen

4
Evet, orada sonsuza kadar asılı. İşçilerimin hepsi bitiyor (işçi işlevi içindeki döngü sona eriyor, daha sonra tüm çalışanlar için baskı bildirimi yazdırılıyor). Birleştirme hiçbir şey yapmaz. Eğer Queuejoin()
işlevimden çıkarırsam

@LaurensKoppenol Belki aramadan queue.put(ret)önce aramıyor musunuz p.start()? Bu durumda, iş parçacığı queue.get()sonsuza kadar asılı kalacaktır . Yorum yaparken yukarıdaki kod parçamı kopyalayarak bunu çoğaltabilirsiniz queue.put(ret).
Matthew Moisen

Ben bu cevabı düzenledim, queue.get()önce olmalı p.join(). Şimdi benim için çalışıyor.
jfunk


10

exitBir işlemin çıkış kodunu ayarlamak için yerleşik olanı kullanabilirsiniz. exitcodeSürecin özelliğinden elde edilebilir :

import multiprocessing

def worker(procnum):
    print str(procnum) + ' represent!'
    exit(procnum)

if __name__ == '__main__':
    jobs = []
    for i in range(5):
        p = multiprocessing.Process(target=worker, args=(i,))
        jobs.append(p)
        p.start()

    result = []
    for proc in jobs:
        proc.join()
        result.append(proc.exitcode)
    print result

Çıktı:

0 represent!
1 represent!
2 represent!
3 represent!
4 represent!
[0, 1, 2, 3, 4]

4
Bu yaklaşımın kafa karıştırıcı olabileceği konusunda uyarılmalıdır. İşlemler genellikle çıkış kodu 0 ile çıkmalı ve hatasız olarak tamamlanmalıdır. Sistem işlem çıkış kodlarınızı izleyen herhangi bir şey varsa, bunların hata olarak raporlandığını görebilirsiniz.
ferrouswheel

1
Hata durumunda üst işlemde bir istisna oluşturmak istiyorsanız mükemmeldir.
crizCraig


3

Py3.6'da benim için çalışarak yukarıdan kopyalanan en basit örnekleri basitleştireceğimi düşündüm. En basit olanı multiprocessing.Pool:

import multiprocessing
import time

def worker(x):
    time.sleep(1)
    return x

pool = multiprocessing.Pool()
print(pool.map(worker, range(10)))

Havuzdaki işlem sayısını, örn Pool(processes=5). Ancak varsayılan olarak CPU sayımına sahiptir, bu nedenle CPU'ya bağlı görevler için boş bırakın. (G / Ç-bağlı görevler genellikle iş parçacıkları genellikle bekler, çünkü iş parçacıkları çoğunlukla bir CPU çekirdeğini paylaşabilir.) PoolAyrıca parçalama optimizasyonu uygular .

(Worker yöntemi bir yöntem içinde yuvalanamaz unutmayın. Ben başlangıçta benim yöntem pool.maptüm kendi kendine yeten tutmak için , çağrı yapan yöntem içinde tanımladı , ancak sonra süreçler onu alamadım ve attı "AttributeError : External_method..inner_method yerel nesnesi seçilemiyor. "Daha fazla burada . Bir sınıfın içinde olabilir.)

(Baskı 'represent!'yerine belirtilen orijinal soruyu takdir edin time.sleep(), ancak onsuz bazı kodların aynı anda çalıştığını düşündüm.)


Py3'ler ProcessPoolExecutorde iki satırdır ( .mapbir jeneratör döndürür, böylece ihtiyacınız olur list()):

from concurrent.futures import ProcessPoolExecutor
with ProcessPoolExecutor() as executor:
    print(list(executor.map(worker, range(10))))

Düz Processes ile:

import multiprocessing
import time

def worker(x, queue):
    time.sleep(1)
    queue.put(x)

queue = multiprocessing.SimpleQueue()
tasks = range(10)

for task in tasks:
    multiprocessing.Process(target=worker, args=(task, queue,)).start()

for _ in tasks:
    print(queue.get())

SimpleQueueİhtiyacınız olan tek şey putve ise kullanın get. İlk döngü engelleme queue.getçağrıları yapmadan önce tüm işlemleri başlatır . Aramak için p.join()de bir sebep olduğunu sanmıyorum .


2

Basit bir çözüm:

import multiprocessing

output=[]
data = range(0,10)

def f(x):
    return x**2

def handler():
    p = multiprocessing.Pool(64)
    r=p.map(f, data)
    return r

if __name__ == '__main__':
    output.append(handler())

print(output[0])

Çıktı:

[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

2

Python 3 kullanıyorsanız, concurrent.futures.ProcessPoolExecutoruygun bir soyutlama olarak kullanabilirsiniz :

from concurrent.futures import ProcessPoolExecutor

def worker(procnum):
    '''worker function'''
    print(str(procnum) + ' represent!')
    return procnum


if __name__ == '__main__':
    with ProcessPoolExecutor() as executor:
        print(list(executor.map(worker, range(5))))

Çıktı:

0 represent!
1 represent!
2 represent!
3 represent!
4 represent!
[0, 1, 2, 3, 4]

0

Ben fonksiyondan hata kodları almak gerektiğinden vartec cevap biraz değiştirdim. (Teşekkürler vertec !!! harika bir numara)

Bu bir ile de yapılabilir, manager.listancak bir dikte içinde olması ve içindeki bir listeyi saklamak daha iyidir. Bu şekilde, listenin yerleştirileceği sıradan emin olamadığımız için işlevi ve sonuçları tutarız.

from multiprocessing import Process
import time
import datetime
import multiprocessing


def func1(fn, m_list):
    print 'func1: starting'
    time.sleep(1)
    m_list[fn] = "this is the first function"
    print 'func1: finishing'
    # return "func1"  # no need for return since Multiprocess doesnt return it =(

def func2(fn, m_list):
    print 'func2: starting'
    time.sleep(3)
    m_list[fn] = "this is function 2"
    print 'func2: finishing'
    # return "func2"

def func3(fn, m_list):
    print 'func3: starting'
    time.sleep(9)
    # if fail wont join the rest because it never populate the dict
    # or do a try/except to get something in return.
    raise ValueError("failed here")
    # if we want to get the error in the manager dict we can catch the error
    try:
        raise ValueError("failed here")
        m_list[fn] = "this is third"
    except:
        m_list[fn] = "this is third and it fail horrible"
        # print 'func3: finishing'
        # return "func3"


def runInParallel(*fns):  # * is to accept any input in list
    start_time = datetime.datetime.now()
    proc = []
    manager = multiprocessing.Manager()
    m_list = manager.dict()
    for fn in fns:
        # print fn
        # print dir(fn)
        p = Process(target=fn, name=fn.func_name, args=(fn, m_list))
        p.start()
        proc.append(p)
    for p in proc:
        p.join()  # 5 is the time out

    print datetime.datetime.now() - start_time
    return m_list, proc

if __name__ == '__main__':
    manager, proc = runInParallel(func1, func2, func3)
    # print dir(proc[0])
    # print proc[0]._name
    # print proc[0].name
    # print proc[0].exitcode

    # here you can check what did fail
    for i in proc:
        print i.name, i.exitcode  # name was set up in the Process line 53

    # here will only show the function that worked and where able to populate the 
    # manager dict
    for i, j in manager.items():
        print dir(i)  # things you can do to the function
        print i, j
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.