Làm thế nào tôi có thể sử dụng luồng trong Python?


1281

Tôi đang cố gắng để hiểu luồng trong Python. Tôi đã xem các tài liệu và ví dụ, nhưng thật lòng mà nói, nhiều ví dụ quá phức tạp và tôi gặp khó khăn trong việc hiểu chúng.

Làm thế nào để bạn hiển thị rõ ràng các nhiệm vụ được chia cho đa luồng?


31
Một cuộc thảo luận chung về chủ đề này có thể được tìm thấy trong Bài toán khó nhất của Python bởi Jeff Knupp. Tóm lại, có vẻ như luồng không dành cho người mới bắt đầu.
Matthew Walker

112
haha, tôi có xu hướng nghĩ rằng xâu chuỗi là dành cho tất cả mọi người, nhưng người mới bắt đầu không phải để xâu chuỗi :)))))
Bohdan

42
Chỉ cần gắn cờ là mọi người nên đọc tất cả các câu trả lời vì những câu trả lời sau sẽ tốt hơn vì các tính năng ngôn ngữ mới được tận dụng ...
Gwyn Evans

5
Hãy nhớ viết logic cốt lõi của bạn bằng C và gọi nó qua ctypes để thực sự tận dụng luồng Python.
aaa90210

4
Tôi chỉ muốn thêm rằng PyPubSub là một cách tuyệt vời để gửi và nhận tin nhắn để kiểm soát luồng luồng
ytpillai

Câu trả lời:


1418

Vì câu hỏi này đã được hỏi vào năm 2010, đã có sự đơn giản hóa thực sự trong cách thực hiện đa luồng đơn giản với Python với bản đồnhóm .

Mã dưới đây xuất phát từ một bài viết / bài đăng trên blog mà bạn chắc chắn nên kiểm tra (không liên kết) - Tính song song trong một dòng: Một mô hình tốt hơn cho các nhiệm vụ luồng ngày . Tôi sẽ tóm tắt dưới đây - cuối cùng chỉ là một vài dòng mã:

from multiprocessing.dummy import Pool as ThreadPool
pool = ThreadPool(4)
results = pool.map(my_function, my_array)

Phiên bản đa luồng của:

results = []
for item in my_array:
    results.append(my_function(item))

Sự miêu tả

Bản đồ là một chức năng nhỏ thú vị và là chìa khóa để dễ dàng đưa tính song song vào mã Python của bạn. Đối với những người không quen thuộc, bản đồ là thứ được lấy từ các ngôn ngữ chức năng như Lisp. Đây là một chức năng ánh xạ một chức năng khác qua một chuỗi.

Map xử lý việc lặp lại theo trình tự cho chúng tôi, áp dụng chức năng và lưu trữ tất cả các kết quả trong một danh sách tiện dụng ở cuối.

Nhập mô tả hình ảnh ở đây


Thực hiện

Các phiên bản song song của chức năng bản đồ được cung cấp bởi hai thư viện: đa xử lý, và cũng là bước con ít được biết đến nhưng cũng không kém phần tuyệt vời của nó: Multiprocessing.dummy.

multiprocessing.dummyhoàn toàn giống với mô-đun đa xử lý, nhưng sử dụng các luồng thay thế ( một sự khác biệt quan trọng - sử dụng nhiều quy trình cho các tác vụ cần nhiều CPU; các luồng cho (và trong) I / O ):

Multiprocessing.dummy sao chép API của đa xử lý, nhưng không nhiều hơn một trình bao bọc xung quanh mô-đun luồng.

import urllib2
from multiprocessing.dummy import Pool as ThreadPool

urls = [
  'http://www.python.org',
  'http://www.python.org/about/',
  'http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html',
  'http://www.python.org/doc/',
  'http://www.python.org/download/',
  'http://www.python.org/getit/',
  'http://www.python.org/community/',
  'https://wiki.python.org/moin/',
]

# Make the Pool of workers
pool = ThreadPool(4)

# Open the URLs in their own threads
# and return the results
results = pool.map(urllib2.urlopen, urls)

# Close the pool and wait for the work to finish
pool.close()
pool.join()

Và kết quả thời gian:

Single thread:   14.4 seconds
       4 Pool:   3.1 seconds
       8 Pool:   1.4 seconds
      13 Pool:   1.3 seconds

Truyền nhiều đối số (chỉ hoạt động như thế này trong Python 3.3 trở lên ):

Để vượt qua nhiều mảng:

results = pool.starmap(function, zip(list_a, list_b))

Hoặc để vượt qua một hằng và một mảng:

results = pool.starmap(function, zip(itertools.repeat(constant), list_a))

Nếu bạn đang sử dụng phiên bản Python trước đó, bạn có thể chuyển nhiều đối số thông qua cách giải quyết này ).

(Cảm ơn người dùng136036 vì đã nhận xét hữu ích.)


90
Điều này chỉ thiếu phiếu bầu vì nó được đăng mới. Câu trả lời này hoạt động rất hay và thể hiện chức năng 'bản đồ' mang lại cú pháp dễ hiểu hơn nhiều so với các câu trả lời khác ở đây.
nhàn rỗi

25
Đây có phải là chủ đề thậm chí không phải là quá trình? Có vẻ như nó cố gắng đa xử lý! =
Đa

72
Nhân tiện, các bạn, bạn cũng có thể viết with Pool(8) as p: p.map( *whatever* )và thoát khỏi các dòng sổ sách kế toán.

11
@BarafuAlbino: Hữu ích vì điều đó có lẽ đáng lưu ý rằng điều này chỉ hoạt động trong Python 3.3+ .
fuglede

9
Làm thế nào bạn có thể để lại câu trả lời này và không đề cập rằng điều này chỉ hữu ích cho các hoạt động I / O? Điều này chỉ chạy trên một chủ đề duy nhất vô dụng đối với hầu hết các trường hợp và thực sự chậm hơn so với chỉ thực hiện theo cách thông thường
Frobot

714

Đây là một ví dụ đơn giản: bạn cần thử một vài URL thay thế và trả lại nội dung của cái đầu tiên để trả lời.

import Queue
import threading
import urllib2

# Called by each thread
def get_url(q, url):
    q.put(urllib2.urlopen(url).read())

theurls = ["http://google.com", "http://yahoo.com"]

q = Queue.Queue()

for u in theurls:
    t = threading.Thread(target=get_url, args = (q,u))
    t.daemon = True
    t.start()

s = q.get()
print s

Đây là trường hợp phân luồng được sử dụng như một tối ưu hóa đơn giản: mỗi luồng con đang chờ một URL để giải quyết và phản hồi, để đưa nội dung của nó vào hàng đợi; mỗi luồng là một daemon (sẽ không tiếp tục quá trình nếu luồng chính kết thúc - điều đó phổ biến hơn không); luồng chính bắt đầu tất cả các chuỗi con, thực hiện một gethàng đợi để đợi cho đến khi một trong số chúng thực hiện xong put, sau đó phát ra kết quả và chấm dứt (sẽ loại bỏ mọi chuỗi con vẫn có thể đang chạy, vì chúng là các luồng daemon).

Việc sử dụng đúng các luồng trong Python luôn được kết nối với các hoạt động I / O (vì CPython không sử dụng nhiều lõi để chạy các tác vụ bị ràng buộc bởi CPU, lý do duy nhất để phân luồng là không chặn tiến trình trong khi phải chờ một số I / O ). Hàng đợi gần như là cách tốt nhất để tạo ra công việc cho các chủ đề và / hoặc thu thập kết quả của công việc, và về bản chất chúng là các chủ đề an toàn, vì vậy chúng giúp bạn không phải lo lắng về các khóa, điều kiện, sự kiện, ngữ nghĩa và các nội dung khác -thread phối hợp / khái niệm truyền thông.


10
Cảm ơn một lần nữa, MartelliBot. Tôi đã cập nhật ví dụ để chờ đợi cho tất cả để url để đáp ứng: nhập khẩu Queue, ren, urllib2 q = Queue.Queue () url = '' ' a.com B.com . C.com ''' chia () urls_received = 0 def get_url (q, url): req = urllib2.Request (url) resp = urllib2.urlopen (req) q.put (resp.read ()) urls_receured urls_receured + = 1 in urls_receured = threading.Thread (target = get_url, args = (q, u)) t.daemon = True t.start () trong khi q.empty () và urls_receured <len (url): s = q.get () print s
htmldrum

3
@JRM: nếu bạn xem câu trả lời tiếp theo bên dưới, tôi nghĩ rằng cách tốt hơn để đợi cho đến khi các luồng kết thúc sẽ sử dụng join()phương thức này, vì điều đó sẽ làm cho luồng chính chờ cho đến khi chúng được thực hiện mà không cần sử dụng bộ xử lý liên tục kiểm tra giá trị. @Alex: cảm ơn, đây chính xác là những gì tôi cần để hiểu cách sử dụng chủ đề.
krs013

6
Đối với python3, thay thế 'nhập urllib2' bằng 'nhập urllib.request thành urllib2'. và đặt dấu ngoặc đơn trong câu lệnh in.
Harvey

5
Đối với python 3 thay thế Queuetên mô-đun bằng queue. Tên phương thức là như nhau.
JSmyth

2
Tôi lưu ý rằng giải pháp sẽ chỉ in ra một trong các trang. Để in cả hai trang từ hàng đợi, chỉ cần chạy lại lệnh: s = q.get() print s @ krs013 Bạn không cần joinvì Queue.get () đang chặn.
Tom Anderson

256

LƯU Ý : Để thực hiện song song hóa thực tế trong Python, bạn nên sử dụng mô đun đa xử lý để phân tách nhiều quá trình thực thi song song (do khóa trình thông dịch toàn cầu, các luồng Python cung cấp xen kẽ, nhưng thực tế chúng được thực hiện một cách thanh thản, không song song và chỉ hữu ích khi xen kẽ các hoạt động I / O).

Tuy nhiên, nếu bạn chỉ đang tìm kiếm xen kẽ (hoặc đang thực hiện các thao tác I / O có thể song song mặc dù khóa trình thông dịch toàn cầu), thì mô-đun luồng là nơi bắt đầu. Như một ví dụ thực sự đơn giản, chúng ta hãy xem xét vấn đề tổng hợp một phạm vi lớn bằng cách tóm tắt các phần phụ song song:

import threading

class SummingThread(threading.Thread):
     def __init__(self,low,high):
         super(SummingThread, self).__init__()
         self.low=low
         self.high=high
         self.total=0

     def run(self):
         for i in range(self.low,self.high):
             self.total+=i


thread1 = SummingThread(0,500000)
thread2 = SummingThread(500000,1000000)
thread1.start() # This actually causes the thread to run
thread2.start()
thread1.join()  # This waits until the thread has completed
thread2.join()
# At this point, both threads have completed
result = thread1.total + thread2.total
print result

Lưu ý rằng ở trên là một ví dụ rất ngu ngốc, vì nó hoàn toàn không có I / O và sẽ được thực thi mặc dù được xen kẽ (với chi phí chuyển đổi ngữ cảnh bổ sung) trong CPython do khóa trình thông dịch toàn cầu.


16
@Alex, tôi không nói nó thực tế, nhưng nó thể hiện cách xác định và sinh ra các chủ đề, mà tôi nghĩ là những gì OP muốn.
Michael Aaron Safyan

6
Mặc dù điều này không chỉ ra cách xác định và sinh ra các luồng, nhưng thực tế nó không tổng hợp các chuỗi con song song. thread1chạy cho đến khi nó hoàn thành trong khi khối luồng chính, sau đó điều tương tự xảy ra thread2, sau đó luồng chính sẽ tiếp tục và in ra các giá trị mà chúng tích lũy.
martineau

Có nên như super(SummingThread, self).__init__()vậy không? Như trong stackoverflow.com/a/2197625/806988
James Andres

@JamesAndres, giả sử rằng không ai thừa kế từ "SummingThread", thì một trong hai sẽ hoạt động tốt; trong trường hợp siêu như vậy (SummingThread, self) chỉ là một cách ưa thích để tra cứu lớp tiếp theo theo thứ tự độ phân giải phương thức (MRO), đó là luồng .Thread (và sau đó gọi init trên đó trong cả hai trường hợp). Tuy nhiên, bạn đã đúng khi sử dụng super () là phong cách tốt hơn cho Python hiện tại. Super là tương đối gần đây tại thời điểm tôi cung cấp câu trả lời này, do đó gọi trực tiếp đến siêu hạng hơn là sử dụng super (). Tôi sẽ cập nhật điều này để sử dụng siêu, mặc dù.
Michael Aaron Safyan

14
CẢNH BÁO: Không sử dụng đa luồng trong các tác vụ như thế này! Như được hiển thị bởi Dave Beazley: dabeaz.com/python/NewGIL.pdf , 2 luồng python trên 2 CPU thực hiện một nhiệm vụ nặng CPU 2 lần SLOWER hơn 1 luồng trên 1 CPU và 1,5 lần SLOWER hơn 2 luồng trên 1 CPU. Hành vi kỳ quái này là do sự phối hợp sai giữa các nỗ lực giữa HĐH và Python. Một trường hợp sử dụng thực tế cho các chủ đề là một nhiệm vụ nặng nề I / O. Ví dụ, khi bạn thực hiện đọc / ghi qua mạng, sẽ rất hợp lý khi đặt một luồng, chờ dữ liệu được đọc / ghi, để làm nền và chuyển CPU sang luồng khác, cần xử lý dữ liệu.
Boris Burkov

98

Giống như những người khác được đề cập, CPython chỉ có thể sử dụng các luồng cho I / O chờ do GIL .

Nếu bạn muốn hưởng lợi từ nhiều lõi cho các tác vụ gắn với CPU, hãy sử dụng đa xử lý :

from multiprocessing import Process

def f(name):
    print 'hello', name

if __name__ == '__main__':
    p = Process(target=f, args=('bob',))
    p.start()
    p.join()

33
bạn có thể giải thích một chút những gì nó làm?
pandita

5
@pandita: mã tạo ra một quy trình, sau đó bắt đầu nó. Vì vậy, bây giờ có hai điều xảy ra cùng một lúc: dòng chính của chương trình và quá trình bắt đầu với mục tiêu, fchức năng. Song song, chương trình chính bây giờ chỉ chờ quá trình thoát ra, jointiếp tục với nó. Nếu phần chính vừa thoát, quy trình con có thể hoặc không thể chạy đến khi hoàn thành, do đó, việc thực hiện joinluôn được khuyến nghị.
johntellsall

1
Một câu trả lời mở rộng bao gồm mapchức năng có ở đây: stackoverflow.com/a/28463266/2327328
philshem 9/03/2015

2
@philshem Hãy cẩn thận b / c liên kết bạn đã đăng đang sử dụng nhóm chủ đề (không phải quy trình) như được đề cập ở đây stackoverflow.com/questions/26432411/ . Tuy nhiên, câu trả lời này đang sử dụng một quá trình. Tôi chưa quen với công cụ này, nhưng có vẻ như (do GIL), bạn sẽ chỉ nhận được hiệu suất tăng trong các tình huống cụ thể khi sử dụng đa luồng trong Python. Tuy nhiên, sử dụng nhóm quy trình có thể tận dụng bộ xử lý đa lõi bằng cách có nhiều hơn 1 lõi hoạt động trên một quy trình.
dùng3731622

3
Đây là câu trả lời tốt nhất cho việc thực sự làm điều gì đó hữu ích và tận dụng nhiều lõi CPU
Frobot

92

Chỉ cần một lưu ý: Một hàng đợi là không cần thiết cho luồng.

Đây là ví dụ đơn giản nhất tôi có thể tưởng tượng cho thấy 10 quy trình đang chạy đồng thời.

import threading
from random import randint
from time import sleep


def print_number(number):

    # Sleeps a random 1 to 10 seconds
    rand_int_var = randint(1, 10)
    sleep(rand_int_var)
    print "Thread " + str(number) + " slept for " + str(rand_int_var) + " seconds"

thread_list = []

for i in range(1, 10):

    # Instantiates the thread
    # (i) does not make a sequence, so (i,)
    t = threading.Thread(target=print_number, args=(i,))
    # Sticks the thread in a list so that it remains accessible
    thread_list.append(t)

# Starts threads
for thread in thread_list:
    thread.start()

# This blocks the calling thread until the thread whose join() method is called is terminated.
# From http://docs.python.org/2/library/threading.html#thread-objects
for thread in thread_list:
    thread.join()

# Demonstrates that the main process waited for threads to complete
print "Done"

3
Thêm trích dẫn cuối cùng vào "Xong để làm cho nó in" Xong "
iChux

1
Tôi thích ví dụ này tốt hơn Martelli, dễ chơi hơn. Tuy nhiên, tôi khuyên bạn nên inNumber làm như sau, để làm cho nó rõ hơn một chút những gì đang diễn ra: nó nên lưu randint vào một biến trước khi ngủ trên đó, và sau đó in nên thay đổi thành "Thread" + str ( số) + "ngủ trong" + theRandintVariable + "giây"
Nickolai 17/12/14

Có cách nào để biết khi nào mỗi chủ đề đã kết thúc, khi nó kết thúc?
Matt

1
@Matt Có một vài cách để làm một cái gì đó như thế, nhưng nó sẽ phụ thuộc vào nhu cầu của bạn. Một cách sẽ là cập nhật một singleton hoặc một số biến có thể truy cập công khai khác đang được theo dõi trong một vòng lặp while và được cập nhật ở cuối chuỗi.
Douglas Adams

2
Không cần forvòng lặp thứ hai , bạn có thể gọi thread.start()trong vòng lặp đầu tiên.
Đánh dấu Mishyn

49

Câu trả lời từ Alex Martelli đã giúp tôi. Tuy nhiên, đây là một phiên bản sửa đổi mà tôi nghĩ là hữu ích hơn (ít nhất là với tôi).

Đã cập nhật: hoạt động trong cả Python 2 và Python 3

try:
    # For Python 3
    import queue
    from urllib.request import urlopen
except:
    # For Python 2 
    import Queue as queue
    from urllib2 import urlopen

import threading

worker_data = ['http://google.com', 'http://yahoo.com', 'http://bing.com']

# Load up a queue with your data. This will handle locking
q = queue.Queue()
for url in worker_data:
    q.put(url)

# Define a worker function
def worker(url_queue):
    queue_full = True
    while queue_full:
        try:
            # Get your data off the queue, and do some work
            url = url_queue.get(False)
            data = urlopen(url).read()
            print(len(data))

        except queue.Empty:
            queue_full = False

# Create as many threads as you want
thread_count = 5
for i in range(thread_count):
    t = threading.Thread(target=worker, args = (q,))
    t.start()

6
Tại sao không phá vỡ ngoại lệ?
Stavros Korokithakis

1
bạn có thể, chỉ là sở thích cá nhân
JimJty

1
Tôi chưa chạy mã, nhưng bạn không cần phải tạo các chủ đề? Tôi nghĩ rằng sau vòng lặp cuối cùng đó, chương trình của bạn có thể thoát - ít nhất là vì đó là cách các luồng hoạt động. Tôi nghĩ rằng một cách tiếp cận tốt hơn là không đưa dữ liệu worker vào hàng đợi, mà đặt đầu ra vào hàng đợi bởi vì sau đó bạn có thể có một mainloop không chỉ xử lý thông tin đi vào hàng đợi từ các worker, mà bây giờ nó cũng không phải là luồng, và bạn biết nó sẽ không thoát sớm.
dylnmc

1
@dylnmc, nằm ngoài trường hợp sử dụng của tôi (hàng đợi đầu vào của tôi được xác định trước). Nếu bạn muốn đi theo con đường của mình, tôi khuyên bạn nên xem cần tây
JimJty

@JimJty em biết tại sao tôi nhận được lỗi này: import Queue ModuleNotFoundError: No module named 'Queue'Tôi đang chạy python 3.6.5 một số bài viết đề cập rằng trong python 3.6.5 nó được queuenhưng ngay cả sau khi tôi thay đổi nó, vẫn không làm việc
user9371654

25

Đưa ra một chức năng f, luồng nó như thế này:

import threading
threading.Thread(target=f).start()

Để truyền đối số cho f

threading.Thread(target=f, args=(a,b,c)).start()

Điều này rất đơn giản. Làm thế nào để bạn đảm bảo rằng các chủ đề đóng khi bạn được thực hiện với chúng?
cameronroytaylor

Theo tôi hiểu, khi chức năng thoát khỏi Threadđối tượng sẽ dọn sạch. Xem tài liệu . Có một is_alive()phương pháp bạn có thể sử dụng để kiểm tra một chủ đề nếu bạn cần.
starfry

Tôi đã thấy is_alivephương pháp này, nhưng tôi không thể tìm ra cách áp dụng nó vào luồng. Tôi đã thử gán thread1=threading.Thread(target=f).start()và sau đó kiểm tra nó thread1.is_alive(), nhưng thread1được điền với None, vì vậy không có may mắn ở đó. Bạn có biết nếu có bất kỳ cách nào khác để truy cập chủ đề?
cameronroytaylor

4
Bạn cần gán đối tượng luồng cho một biến và sau đó khởi động nó bằng cách sử dụng varaible đó: thread1=threading.Thread(target=f)theo sau thread1.start(). Sau đó, bạn có thể làm thread1.is_alive().
starfry

1
Điều đó đã làm việc. Và có, kiểm tra với thread1.is_alive()trả về Falsengay khi chức năng thoát.
cameronroytaylor

25

Tôi thấy điều này rất hữu ích: tạo nhiều luồng như lõi và để chúng thực thi một số lượng lớn (trong trường hợp này), gọi một chương trình shell):

import Queue
import threading
import multiprocessing
import subprocess

q = Queue.Queue()
for i in range(30): # Put 30 tasks in the queue
    q.put(i)

def worker():
    while True:
        item = q.get()
        # Execute a task: call a shell program and wait until it completes
        subprocess.call("echo " + str(item), shell=True)
        q.task_done()

cpus = multiprocessing.cpu_count() # Detect number of cores
print("Creating %d threads" % cpus)
for i in range(cpus):
     t = threading.Thread(target=worker)
     t.daemon = True
     t.start()

q.join() # Block until all tasks are done

@shavenwarthog chắc chắn người ta có thể điều chỉnh biến "cpus" tùy theo nhu cầu của một người. Dù sao, lệnh gọi quy trình con sẽ sinh ra các quy trình con và chúng sẽ được phân bổ cpus bởi HĐH ("tiến trình cha" của python không có nghĩa là "cùng CPU" cho các quy trình con).
cá heo

2
bạn đã đúng, nhận xét của tôi về "các luồng được bắt đầu trên cùng CPU với tiến trình cha" là sai. Cảm ơn vi đa trả lơi!
johntellsall

1
có thể đáng lưu ý rằng không giống như đa luồng sử dụng cùng một không gian bộ nhớ, đa xử lý không thể chia sẻ các biến / dữ liệu một cách dễ dàng. +1 mặc dù.
tưởng tượng

22

Python 3 có khả năng khởi chạy các tác vụ song song . Điều này làm cho công việc của chúng tôi dễ dàng hơn.

Nó có tổng hợp chủ đềquy trình tổng hợp .

Sau đây cung cấp một cái nhìn sâu sắc:

Ví dụ ThreadPoolExecutor ( nguồn )

import concurrent.futures
import urllib.request

URLS = ['http://www.foxnews.com/',
        'http://www.cnn.com/',
        'http://europe.wsj.com/',
        'http://www.bbc.co.uk/',
        'http://some-made-up-domain.com/']

# Retrieve a single page and report the URL and contents
def load_url(url, timeout):
    with urllib.request.urlopen(url, timeout=timeout) as conn:
        return conn.read()

# We can use a with statement to ensure threads are cleaned up promptly
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
    # Start the load operations and mark each future with its URL
    future_to_url = {executor.submit(load_url, url, 60): url for url in URLS}
    for future in concurrent.futures.as_completed(future_to_url):
        url = future_to_url[future]
        try:
            data = future.result()
        except Exception as exc:
            print('%r generated an exception: %s' % (url, exc))
        else:
            print('%r page is %d bytes' % (url, len(data)))

ProcessPoolExecutor ( nguồn )

import concurrent.futures
import math

PRIMES = [
    112272535095293,
    112582705942171,
    112272535095293,
    115280095190773,
    115797848077099,
    1099726899285419]

def is_prime(n):
    if n % 2 == 0:
        return False

    sqrt_n = int(math.floor(math.sqrt(n)))
    for i in range(3, sqrt_n + 1, 2):
        if n % i == 0:
            return False
    return True

def main():
    with concurrent.futures.ProcessPoolExecutor() as executor:
        for number, prime in zip(PRIMES, executor.map(is_prime, PRIMES)):
            print('%d is prime: %s' % (number, prime))

if __name__ == '__main__':
    main()

18

Sử dụng mô-đun đồng thời mới rực cháy.

def sqr(val):
    import time
    time.sleep(0.1)
    return val * val

def process_result(result):
    print(result)

def process_these_asap(tasks):
    import concurrent.futures

    with concurrent.futures.ProcessPoolExecutor() as executor:
        futures = []
        for task in tasks:
            futures.append(executor.submit(sqr, task))

        for future in concurrent.futures.as_completed(futures):
            process_result(future.result())
        # Or instead of all this just do:
        # results = executor.map(sqr, tasks)
        # list(map(process_result, results))

def main():
    tasks = list(range(10))
    print('Processing {} tasks'.format(len(tasks)))
    process_these_asap(tasks)
    print('Done')
    return 0

if __name__ == '__main__':
    import sys
    sys.exit(main())

Cách tiếp cận của người thực thi có vẻ quen thuộc với tất cả những người đã nhúng tay vào Java trước đây.

Ngoài ra, một lưu ý phụ: Để giữ cho vũ trụ lành mạnh, đừng quên đóng các nhóm / người thực thi của bạn nếu bạn không sử dụng withbối cảnh (điều này thật tuyệt vời mà nó mang lại cho bạn)


17

Đối với tôi, ví dụ hoàn hảo cho phân luồng là theo dõi các sự kiện không đồng bộ. Nhìn vào mã này.

# thread_test.py
import threading
import time

class Monitor(threading.Thread):
    def __init__(self, mon):
        threading.Thread.__init__(self)
        self.mon = mon

    def run(self):
        while True:
            if self.mon[0] == 2:
                print "Mon = 2"
                self.mon[0] = 3;

Bạn có thể chơi với mã này bằng cách mở một phiên IPython và làm một cái gì đó như:

>>> from thread_test import Monitor
>>> a = [0]
>>> mon = Monitor(a)
>>> mon.start()
>>> a[0] = 2
Mon = 2
>>>a[0] = 2
Mon = 2

Đợi vài phút

>>> a[0] = 2
Mon = 2

1
AttributionError: 'Đối tượng giám sát' không có thuộc tính 'stop'?
pandita

5
Không phải bạn đang làm hỏng chu kỳ CPU trong khi chờ đợi sự kiện của bạn xảy ra sao? Không phải luôn luôn là một điều rất thực tế để làm.
ông trùm

3
Giống như ông trùm nói, điều này sẽ liên tục được thực thi. Tối thiểu bạn có thể thêm vào một giấc ngủ ngắn, giả sử ngủ (0,1), điều này có thể sẽ làm giảm đáng kể việc sử dụng cpu trên một ví dụ đơn giản như thế này.
tưởng tượng

3
Đây là một ví dụ khủng khiếp, lãng phí một cốt lõi. Thêm một giấc ngủ ít nhất nhưng giải pháp thích hợp là sử dụng một số cơ chế báo hiệu.
PureW

16

Hầu hết các tài liệu và hướng dẫn sử dụng Python ThreadingQueuemô-đun, và chúng có vẻ áp đảo cho người mới bắt đầu.

Có lẽ hãy xem xét concurrent.futures.ThreadPoolExecutormô-đun của Python 3.

Kết hợp với withmệnh đề và danh sách hiểu nó có thể là một sự quyến rũ thực sự.

from concurrent.futures import ThreadPoolExecutor, as_completed

def get_url(url):
    # Your actual program here. Using threading.Lock() if necessary
    return ""

# List of URLs to fetch
urls = ["url1", "url2"]

with ThreadPoolExecutor(max_workers = 5) as executor:

    # Create threads
    futures = {executor.submit(get_url, url) for url in urls}

    # as_completed() gives you the threads once finished
    for f in as_completed(futures):
        # Get the results
        rs = f.result()

15

Tôi đã thấy rất nhiều ví dụ ở đây, nơi không có tác phẩm thực sự nào được thực hiện, và chúng chủ yếu được gắn với CPU. Dưới đây là một ví dụ về tác vụ gắn với CPU, tính toán tất cả các số nguyên tố trong khoảng từ 10 triệu đến 10,05 triệu. Tôi đã sử dụng tất cả bốn phương pháp ở đây:

import math
import timeit
import threading
import multiprocessing
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor


def time_stuff(fn):
    """
    Measure time of execution of a function
    """
    def wrapper(*args, **kwargs):
        t0 = timeit.default_timer()
        fn(*args, **kwargs)
        t1 = timeit.default_timer()
        print("{} seconds".format(t1 - t0))
    return wrapper

def find_primes_in(nmin, nmax):
    """
    Compute a list of prime numbers between the given minimum and maximum arguments
    """
    primes = []

    # Loop from minimum to maximum
    for current in range(nmin, nmax + 1):

        # Take the square root of the current number
        sqrt_n = int(math.sqrt(current))
        found = False

        # Check if the any number from 2 to the square root + 1 divides the current numnber under consideration
        for number in range(2, sqrt_n + 1):

            # If divisible we have found a factor, hence this is not a prime number, lets move to the next one
            if current % number == 0:
                found = True
                break

        # If not divisible, add this number to the list of primes that we have found so far
        if not found:
            primes.append(current)

    # I am merely printing the length of the array containing all the primes, but feel free to do what you want
    print(len(primes))

@time_stuff
def sequential_prime_finder(nmin, nmax):
    """
    Use the main process and main thread to compute everything in this case
    """
    find_primes_in(nmin, nmax)

@time_stuff
def threading_prime_finder(nmin, nmax):
    """
    If the minimum is 1000 and the maximum is 2000 and we have four workers,
    1000 - 1250 to worker 1
    1250 - 1500 to worker 2
    1500 - 1750 to worker 3
    1750 - 2000 to worker 4
    so let’s split the minimum and maximum values according to the number of workers
    """
    nrange = nmax - nmin
    threads = []
    for i in range(8):
        start = int(nmin + i * nrange/8)
        end = int(nmin + (i + 1) * nrange/8)

        # Start the thread with the minimum and maximum split up to compute
        # Parallel computation will not work here due to the GIL since this is a CPU-bound task
        t = threading.Thread(target = find_primes_in, args = (start, end))
        threads.append(t)
        t.start()

    # Don’t forget to wait for the threads to finish
    for t in threads:
        t.join()

@time_stuff
def processing_prime_finder(nmin, nmax):
    """
    Split the minimum, maximum interval similar to the threading method above, but use processes this time
    """
    nrange = nmax - nmin
    processes = []
    for i in range(8):
        start = int(nmin + i * nrange/8)
        end = int(nmin + (i + 1) * nrange/8)
        p = multiprocessing.Process(target = find_primes_in, args = (start, end))
        processes.append(p)
        p.start()

    for p in processes:
        p.join()

@time_stuff
def thread_executor_prime_finder(nmin, nmax):
    """
    Split the min max interval similar to the threading method, but use a thread pool executor this time.
    This method is slightly faster than using pure threading as the pools manage threads more efficiently.
    This method is still slow due to the GIL limitations since we are doing a CPU-bound task.
    """
    nrange = nmax - nmin
    with ThreadPoolExecutor(max_workers = 8) as e:
        for i in range(8):
            start = int(nmin + i * nrange/8)
            end = int(nmin + (i + 1) * nrange/8)
            e.submit(find_primes_in, start, end)

@time_stuff
def process_executor_prime_finder(nmin, nmax):
    """
    Split the min max interval similar to the threading method, but use the process pool executor.
    This is the fastest method recorded so far as it manages process efficiently + overcomes GIL limitations.
    RECOMMENDED METHOD FOR CPU-BOUND TASKS
    """
    nrange = nmax - nmin
    with ProcessPoolExecutor(max_workers = 8) as e:
        for i in range(8):
            start = int(nmin + i * nrange/8)
            end = int(nmin + (i + 1) * nrange/8)
            e.submit(find_primes_in, start, end)

def main():
    nmin = int(1e7)
    nmax = int(1.05e7)
    print("Sequential Prime Finder Starting")
    sequential_prime_finder(nmin, nmax)
    print("Threading Prime Finder Starting")
    threading_prime_finder(nmin, nmax)
    print("Processing Prime Finder Starting")
    processing_prime_finder(nmin, nmax)
    print("Thread Executor Prime Finder Starting")
    thread_executor_prime_finder(nmin, nmax)
    print("Process Executor Finder Starting")
    process_executor_prime_finder(nmin, nmax)

main()

Dưới đây là kết quả trên máy bốn lõi Mac OS X của tôi

Sequential Prime Finder Starting
9.708213827005238 seconds
Threading Prime Finder Starting
9.81836523200036 seconds
Processing Prime Finder Starting
3.2467174359990167 seconds
Thread Executor Prime Finder Starting
10.228896902000997 seconds
Process Executor Finder Starting
2.656402041000547 seconds

1
@TheUnfunCat không có trình thực thi quy trình nào tốt hơn nhiều so với phân luồng cho các tác vụ bị ràng buộc cpu
PirateApp

1
Câu trả lời tuyệt vời anh chàng. Tôi có thể xác nhận rằng trong Python 3.6 trên Windows (ít nhất) ThreadPoolExecutor không có gì tốt cho các tác vụ nặng CPU. Nó không sử dụng lõi để tính toán. Trong khi ProcessPoolExecutor sao chép dữ liệu vào MỌI quá trình nó sinh ra, nó gây chết người cho các ma trận lớn.
Anatoly Alekseev

1
Ví dụ rất hữu ích, nhưng tôi không hiểu nó đã hoạt động như thế nào. Chúng ta cần một if __name__ == '__main__':trước khi cuộc gọi chính, nếu không spawn đo lường bản thân và in Một cố gắng đã được thực hiện để bắt đầu một quá trình mới trước khi ... .
Stein

1
@Stein Tôi tin rằng đó chỉ là một vấn đề trên Windows, mặc dù.
AMC

12

Dưới đây là ví dụ rất đơn giản về nhập CSV bằng cách sử dụng luồng. (Bao gồm thư viện có thể khác nhau cho các mục đích khác nhau.)

Chức năng trợ giúp:

from threading import Thread
from project import app
import csv


def import_handler(csv_file_name):
    thr = Thread(target=dump_async_csv_data, args=[csv_file_name])
    thr.start()

def dump_async_csv_data(csv_file_name):
    with app.app_context():
        with open(csv_file_name) as File:
            reader = csv.DictReader(File)
            for row in reader:
                # DB operation/query

Chức năng điều khiển:

import_handler(csv_file_name)

9

Tôi muốn đóng góp với một ví dụ đơn giản và những lời giải thích tôi thấy hữu ích khi tôi phải tự giải quyết vấn đề này.

Trong câu trả lời này, bạn sẽ tìm thấy một số thông tin về GIL của Python (khóa trình thông dịch toàn cầu) và một ví dụ đơn giản hàng ngày được viết bằng cách sử dụng multrocessing.dummy cộng với một số điểm chuẩn đơn giản.

Khóa phiên dịch toàn cầu (GIL)

Python không cho phép đa luồng theo nghĩa thật nhất của từ này. Nó có một gói đa luồng, nhưng nếu bạn muốn đa luồng để tăng tốc mã của mình, thì thông thường không nên sử dụng nó.

Python có cấu trúc được gọi là khóa trình thông dịch toàn cầu (GIL). GIL đảm bảo rằng chỉ một trong số các 'luồng' của bạn có thể thực thi bất kỳ lúc nào. Một luồng thu được GIL, thực hiện một công việc nhỏ, sau đó chuyển GIL sang luồng tiếp theo.

Điều này xảy ra rất nhanh vì vậy đối với mắt người, có vẻ như các luồng của bạn đang thực thi song song, nhưng chúng thực sự chỉ thay phiên nhau sử dụng cùng lõi CPU.

Tất cả thông qua GIL này thêm chi phí để thực hiện. Điều này có nghĩa là nếu bạn muốn làm cho mã của mình chạy nhanh hơn thì sử dụng gói luồng thường không phải là một ý tưởng hay.

Có nhiều lý do để sử dụng gói luồng của Python. Nếu bạn muốn chạy một số thứ cùng một lúc và hiệu quả không phải là vấn đề đáng lo ngại, thì nó hoàn toàn tốt và tiện lợi. Hoặc nếu bạn đang chạy mã cần chờ một cái gì đó (như một số I / O) thì nó có thể có nhiều ý nghĩa. Nhưng thư viện luồng sẽ không cho phép bạn sử dụng các lõi CPU bổ sung.

Đa luồng có thể được gia công cho hệ điều hành (bằng cách xử lý đa xử lý) và một số ứng dụng bên ngoài gọi mã Python của bạn (ví dụ: Spark hoặc Hadoop ) hoặc một số mã mà mã Python của bạn gọi (ví dụ: bạn có thể có mã Python của bạn gọi một hàm C thực hiện các công cụ đa luồng đắt tiền).

Tại sao vấn đề này

Bởi vì nhiều người dành rất nhiều thời gian để cố gắng tìm ra các nút thắt cổ chai trong mã đa luồng Python ưa thích của họ trước khi họ tìm hiểu GIL là gì.

Khi thông tin này rõ ràng, đây là mã của tôi:

#!/bin/python
from multiprocessing.dummy import Pool
from subprocess import PIPE,Popen
import time
import os

# In the variable pool_size we define the "parallelness".
# For CPU-bound tasks, it doesn't make sense to create more Pool processes
# than you have cores to run them on.
#
# On the other hand, if you are using I/O-bound tasks, it may make sense
# to create a quite a few more Pool processes than cores, since the processes
# will probably spend most their time blocked (waiting for I/O to complete).
pool_size = 8

def do_ping(ip):
    if os.name == 'nt':
        print ("Using Windows Ping to " + ip)
        proc = Popen(['ping', ip], stdout=PIPE)
        return proc.communicate()[0]
    else:
        print ("Using Linux / Unix Ping to " + ip)
        proc = Popen(['ping', ip, '-c', '4'], stdout=PIPE)
        return proc.communicate()[0]


os.system('cls' if os.name=='nt' else 'clear')
print ("Running using threads\n")
start_time = time.time()
pool = Pool(pool_size)
website_names = ["www.google.com","www.facebook.com","www.pinterest.com","www.microsoft.com"]
result = {}
for website_name in website_names:
    result[website_name] = pool.apply_async(do_ping, args=(website_name,))
pool.close()
pool.join()
print ("\n--- Execution took {} seconds ---".format((time.time() - start_time)))

# Now we do the same without threading, just to compare time
print ("\nRunning NOT using threads\n")
start_time = time.time()
for website_name in website_names:
    do_ping(website_name)
print ("\n--- Execution took {} seconds ---".format((time.time() - start_time)))

# Here's one way to print the final output from the threads
output = {}
for key, value in result.items():
    output[key] = value.get()
print ("\nOutput aggregated in a Dictionary:")
print (output)
print ("\n")

print ("\nPretty printed output: ")
for key, value in output.items():
    print (key + "\n")
    print (value)

7

Đây là đa luồng với một ví dụ đơn giản sẽ hữu ích. Bạn có thể chạy nó và hiểu một cách dễ dàng cách đa luồng hoạt động trong Python. Tôi đã sử dụng một khóa để ngăn truy cập vào các luồng khác cho đến khi các luồng trước hoàn thành công việc của họ. Bằng cách sử dụng dòng mã này,

tLock = phân luồng.BoundedSemaphore (value = 4)

bạn có thể cho phép một số quy trình tại một thời điểm và giữ cho các luồng còn lại sẽ chạy sau hoặc sau khi hoàn thành các quy trình trước đó.

import threading
import time

#tLock = threading.Lock()
tLock = threading.BoundedSemaphore(value=4)
def timer(name, delay, repeat):
    print  "\r\nTimer: ", name, " Started"
    tLock.acquire()
    print "\r\n", name, " has the acquired the lock"
    while repeat > 0:
        time.sleep(delay)
        print "\r\n", name, ": ", str(time.ctime(time.time()))
        repeat -= 1

    print "\r\n", name, " is releaseing the lock"
    tLock.release()
    print "\r\nTimer: ", name, " Completed"

def Main():
    t1 = threading.Thread(target=timer, args=("Timer1", 2, 5))
    t2 = threading.Thread(target=timer, args=("Timer2", 3, 5))
    t3 = threading.Thread(target=timer, args=("Timer3", 4, 5))
    t4 = threading.Thread(target=timer, args=("Timer4", 5, 5))
    t5 = threading.Thread(target=timer, args=("Timer5", 0.1, 5))

    t1.start()
    t2.start()
    t3.start()
    t4.start()
    t5.start()

    print "\r\nMain Complete"

if __name__ == "__main__":
    Main()

5

Với việc mượn từ bài đăng này, chúng tôi biết về việc lựa chọn giữa đa luồng, đa xử lý và không đồng bộ / asynciovà cách sử dụng của chúng.

Python 3 có một thư viện tích hợp mới để đồng thời và song song: concallel.futures

Vì vậy, tôi sẽ chứng minh thông qua một thử nghiệm để chạy bốn tác vụ (tức là .sleep()phương thức) theo Threading-Poolcách:

from concurrent.futures import ThreadPoolExecutor, as_completed
from time import sleep, time

def concurrent(max_worker=1):
    futures = []

    tick = time()
    with ThreadPoolExecutor(max_workers=max_worker) as executor:
        futures.append(executor.submit(sleep, 2))  # Two seconds sleep
        futures.append(executor.submit(sleep, 1))
        futures.append(executor.submit(sleep, 7))
        futures.append(executor.submit(sleep, 3))

        for future in as_completed(futures):
            if future.result() is not None:
                print(future.result())

    print('Total elapsed time by {} workers:'.format(max_worker), time()-tick)

concurrent(5)
concurrent(4)
concurrent(3)
concurrent(2)
concurrent(1)

Đầu ra:

Total elapsed time by 5 workers: 7.007831811904907
Total elapsed time by 4 workers: 7.007944107055664
Total elapsed time by 3 workers: 7.003149509429932
Total elapsed time by 2 workers: 8.004627466201782
Total elapsed time by 1 workers: 13.013478994369507

[ LƯU Ý ]:

  • Như bạn có thể thấy trong các kết quả trên, trường hợp tốt nhất là 3 công nhân cho bốn nhiệm vụ đó.
  • Nếu bạn có một tác vụ xử lý thay vì ràng buộc I / O hoặc chặn ( multiprocessingvs threading), bạn có thể thay đổi ThreadPoolExecutorthành ProcessPoolExecutor.

4

Không có giải pháp nào trước đây thực sự sử dụng nhiều lõi trên máy chủ GNU / Linux của tôi (nơi tôi không có quyền quản trị viên). Họ chỉ chạy trên một lõi duy nhất.

Tôi đã sử dụng os.forkgiao diện cấp thấp hơn để sinh ra nhiều quá trình. Đây là mã làm việc cho tôi:

from os import fork

values = ['different', 'values', 'for', 'threads']

for i in range(len(values)):
    p = fork()
    if p == 0:
        my_function(values[i])
        break

2
import threading
import requests

def send():

  r = requests.get('https://www.stackoverlow.com')

thread = []
t = threading.Thread(target=send())
thread.append(t)
t.start()

1
@sP_ Tôi đoán vì sau đó bạn có các đối tượng luồng để bạn có thể đợi chúng kết thúc.
Aleksandar Makragić

1
t = threading.Thread (target = send ()) nên t = threading.Thread (target = send)
TRiNE

Tôi đánh giá thấp câu trả lời này vì nó không cung cấp giải thích về cách cải thiện câu trả lời hiện có, ngoài việc có sự thiếu chính xác nghiêm trọng.
Jules
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.