Sự khác biệt giữa các mô-đun luồng và đa xử lý là gì?


140

Tôi đang học cách sử dụng threadingvà các multiprocessingmô-đun trong Python để chạy song song các hoạt động nhất định và tăng tốc mã của tôi.

Tôi đang gặp khó khăn này (có lẽ vì tôi không có bất kỳ nền tảng lý thuyết nào về nó) để hiểu sự khác biệt giữa một threading.Thread()đối tượng và một đối tượng multiprocessing.Process().

Ngoài ra, tôi không hoàn toàn rõ ràng về cách khởi tạo một chuỗi các công việc và chỉ có 4 (ví dụ) trong số chúng chạy song song, trong khi phần còn lại chờ tài nguyên miễn phí trước khi được thực thi.

Tôi tìm thấy các ví dụ trong tài liệu rõ ràng, nhưng không đầy đủ; Ngay khi tôi cố gắng làm phức tạp mọi thứ một chút, tôi đã nhận được rất nhiều lỗi kỳ lạ (như một phương pháp không thể được xử lý, v.v.).

Vì vậy, khi nào tôi nên sử dụng threadingmultiprocessingcác mô-đun?

Bạn có thể liên kết tôi với một số tài nguyên giải thích các khái niệm đằng sau hai mô-đun này và làm thế nào để sử dụng chúng đúng cách cho các nhiệm vụ phức tạp?


Ngoài ra, còn có Threadmô-đun (được gọi là _threadpython 3.x). Thành thật mà nói, bản thân tôi chưa bao giờ hiểu sự khác biệt ...
Dunno

3
@Dunno: Như Thread/ _threadtài liệu nói rõ ràng, đó là "nguyên thủy cấp thấp". Bạn có thể sử dụng nó để xây dựng các đối tượng đồng bộ hóa tùy chỉnh, để kiểm soát thứ tự nối của một chuỗi các chủ đề, v.v. Nếu bạn không thể tưởng tượng được tại sao bạn cần sử dụng nó, đừng sử dụng nó và tiếp tục threading.
abarnert

Câu trả lời:


260

Những gì Giulio Franco nói là đúng cho đa luồng so với đa xử lý nói chung .

Tuy nhiên, Python * có một vấn đề được thêm vào: Có Khóa phiên dịch toàn cầu ngăn hai luồng trong cùng một quy trình chạy mã Python cùng một lúc. Điều này có nghĩa là nếu bạn có 8 lõi và thay đổi mã của mình để sử dụng 8 luồng, nó sẽ không thể sử dụng CPU 800% và chạy nhanh hơn 8 lần; nó sẽ sử dụng cùng CPU 100% và chạy ở cùng tốc độ. (Trong thực tế, nó sẽ chạy chậm hơn một chút, vì có thêm chi phí từ luồng, ngay cả khi bạn không có bất kỳ dữ liệu chia sẻ nào, nhưng bỏ qua điều đó ngay bây giờ.)

Có nhiều ngoại lệ cho cái này. Nếu tính toán nặng nề của mã của bạn không thực sự xảy ra trong Python, nhưng trong một số thư viện có mã C tùy chỉnh xử lý GIL đúng cách, như ứng dụng gọn gàng, bạn sẽ nhận được lợi ích hiệu suất mong đợi từ luồng. Điều tương tự cũng đúng nếu tính toán nặng được thực hiện bởi một số quy trình con mà bạn chạy và chờ đợi.

Quan trọng hơn, có những trường hợp điều này không quan trọng. Ví dụ: một máy chủ mạng dành phần lớn thời gian để đọc các gói ra khỏi mạng và một ứng dụng GUI dành phần lớn thời gian để chờ đợi các sự kiện của người dùng. Một lý do để sử dụng các luồng trong máy chủ mạng hoặc ứng dụng GUI là cho phép bạn thực hiện các "tác vụ nền" chạy dài mà không ngăn luồng chính tiếp tục dịch vụ gói mạng hoặc sự kiện GUI. Và nó hoạt động tốt với các chủ đề Python. (Về mặt kỹ thuật, điều này có nghĩa là các luồng Python cung cấp cho bạn sự tương tranh, mặc dù chúng không cung cấp cho bạn tính song song cốt lõi.)

Nhưng nếu bạn đang viết một chương trình gắn với CPU bằng Python thuần túy, sử dụng nhiều luồng hơn thường không hữu ích.

Sử dụng các quy trình riêng biệt không có vấn đề như vậy với GIL, vì mỗi quy trình có GIL riêng. Tất nhiên, bạn vẫn có tất cả sự đánh đổi giữa các luồng và quy trình như trong bất kỳ ngôn ngữ nào khác. Việc chia sẻ dữ liệu giữa các tiến trình khó hơn và tốn kém hơn giữa các luồng, có thể tốn kém khi chạy một số lượng lớn các quy trình hoặc để tạo và hủy chúng thường xuyên, v.v. Nhưng GIL cân nhắc rất nhiều về sự cân bằng đối với các quy trình, theo cách không đúng với C, Java. Vì vậy, bạn sẽ thấy mình sử dụng đa xử lý thường xuyên hơn trong Python so với trong C hoặc Java.


Trong khi đó, triết lý "bao gồm pin" của Python mang đến một tin tốt: Viết mã rất dễ dàng có thể được chuyển đổi qua lại giữa các luồng và quy trình với thay đổi một lớp.

Nếu bạn thiết kế mã theo các "công việc" khép kín không chia sẻ bất cứ điều gì với các công việc khác (hoặc chương trình chính) ngoại trừ đầu vào và đầu ra, bạn có thể sử dụng concurrent.futuresthư viện để viết mã của mình xung quanh nhóm luồng như thế này:

with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
    executor.submit(job, argument)
    executor.map(some_function, collection_of_independent_things)
    # ...

Bạn thậm chí có thể nhận được kết quả của những công việc đó và chuyển chúng sang các công việc tiếp theo, chờ đợi mọi thứ theo thứ tự thực hiện hoặc theo thứ tự hoàn thành, v.v.; đọc phần trên Futurecác đối tượng để biết chi tiết.

Bây giờ, nếu hóa ra chương trình của bạn liên tục sử dụng CPU 100% và việc thêm nhiều luồng chỉ làm cho nó chậm hơn, thì bạn đang gặp vấn đề về GIL, vì vậy bạn cần chuyển sang các quy trình. Tất cả bạn phải làm là thay đổi dòng đầu tiên:

with concurrent.futures.ProcessPoolExecutor(max_workers=4) as executor:

Nhắc nhở thực sự duy nhất là các đối số và giá trị trả về của công việc của bạn phải có thể được chọn (và không mất quá nhiều thời gian hoặc bộ nhớ để xử lý) để có thể xử lý chéo. Thông thường đây không phải là một vấn đề, nhưng đôi khi nó là.


Nhưng nếu công việc của bạn không thể khép kín thì sao? Nếu bạn có thể thiết kế mã của mình theo các công việc chuyển thông điệp từ người này sang người khác, điều đó vẫn khá dễ dàng. Bạn có thể phải sử dụng threading.Threadhoặc multiprocessing.Processthay vì dựa vào hồ bơi. Và bạn sẽ phải tạo queue.Queuehoặc multiprocessing.Queuecác đối tượng rõ ràng. (Có rất nhiều tùy chọn khác Các ống, ổ cắm, tập tin có nhiều đàn khác, nhưng vấn đề là, bạn phải làm một cách thủ công nếu phép thuật tự động của Executor không đủ.)

Nhưng nếu bạn thậm chí không thể dựa vào tin nhắn đi qua thì sao? Điều gì xảy ra nếu bạn cần hai công việc để cả hai cùng biến đổi cấu trúc và xem những thay đổi của nhau? Trong trường hợp đó, bạn sẽ cần thực hiện đồng bộ hóa thủ công (khóa, semaphores, điều kiện, v.v.) và, nếu bạn muốn sử dụng các quy trình, các đối tượng bộ nhớ chia sẻ rõ ràng để khởi động. Đây là khi đa luồng (hoặc đa xử lý) gặp khó khăn. Nếu bạn có thể tránh nó, thật tuyệt; nếu bạn không thể, bạn sẽ cần đọc nhiều hơn ai đó có thể đưa vào câu trả lời SO.


Từ một nhận xét, bạn muốn biết có gì khác nhau giữa các luồng và các tiến trình trong Python. Thực sự, nếu bạn đọc câu trả lời của Giulio Franco và của tôi và tất cả các liên kết của chúng tôi, điều đó sẽ bao gồm tất cả mọi thứ, nhưng một bản tóm tắt chắc chắn sẽ hữu ích, vì vậy hãy vào đây:

  1. Chủ đề chia sẻ dữ liệu theo mặc định; quy trình không.
  2. Như một hệ quả của (1), việc gửi dữ liệu giữa các quy trình thường đòi hỏi phải xử lý và giải nén nó. **
  3. Như một hệ quả khác của (1), chia sẻ dữ liệu trực tiếp giữa các quy trình thường yêu cầu đưa nó vào các định dạng cấp thấp như Giá trị, Mảng và ctypescác loại.
  4. Các quy trình không phải chịu GIL.
  5. Trên một số nền tảng (chủ yếu là Windows), các quy trình tốn kém hơn nhiều để tạo và hủy.
  6. Có một số hạn chế bổ sung đối với các quy trình, một số trong đó khác nhau trên các nền tảng khác nhau. Xem hướng dẫn lập trình để biết chi tiết.
  7. Các threadingmô-đun không có một số tính năng của multiprocessingmodule. (Bạn có thể sử dụng multiprocessing.dummyđể có được hầu hết các API bị thiếu trên đầu các chuỗi hoặc bạn có thể sử dụng các mô-đun cấp cao hơn như thế nào concurrent.futuresvà không phải lo lắng về nó.)

* Thực ra không phải Python, ngôn ngữ, có vấn đề này, mà là CPython, cách triển khai "chuẩn" của ngôn ngữ đó. Một số triển khai khác không có GIL, như Jython.

** Nếu bạn đang sử dụng phương thức khởi động ngã ba để đa xử lý mà bạn có thể sử dụng trên hầu hết các nền tảng không phải Windows, thì mỗi quy trình con đều nhận được bất kỳ tài nguyên nào mà cha mẹ có khi trẻ bắt đầu, đó có thể là một cách khác để truyền dữ liệu cho trẻ.


cảm ơn, nhưng tôi không chắc là tôi hiểu tất cả Dù sao, tôi đang cố gắng thực hiện nó một chút cho mục đích học tập và một chút vì với việc sử dụng luồng ngây thơ, tôi đã giảm một nửa tốc độ mã của mình (bắt đầu hơn 1000 luồng cùng lúc, mỗi lần gọi một ứng dụng bên ngoài .. điều này bão hòa cpu, nhưng tốc độ tăng x2). Tôi nghĩ rằng việc quản lý luồng thông minh có thể thực sự cải thiện tốc độ mã của tôi ..
lucacerone

3
@LucaCerone: Ah, nếu mã của bạn dành phần lớn thời gian chờ đợi cho các chương trình bên ngoài, thì có, nó sẽ được hưởng lợi từ luồng. Điểm tốt. Hãy để tôi chỉnh sửa câu trả lời để giải thích điều đó.
abarnert

2
@LucaCerone: Trong khi đó, phần nào bạn không hiểu? Không biết mức độ kiến ​​thức bạn bắt đầu, thật khó để viết một câu trả lời hay, nhưng với một số phản hồi, có lẽ chúng tôi có thể đưa ra một cái gì đó hữu ích cho bạn và cho cả những độc giả tương lai.
abarnert

3
@LucaCerone Bạn nên đọc PEP để đa xử lý tại đây . Nó đưa ra thời gian và ví dụ về các chủ đề so với đa xử lý.
mr2ert

1
@LucaCerone: Nếu đối tượng mà phương thức bị ràng buộc không có bất kỳ trạng thái phức tạp nào, cách giải quyết đơn giản nhất cho vấn đề tẩy là viết một hàm bao bọc ngu ngốc tạo ra đối tượng và gọi phương thức của nó. Nếu nó không có tình trạng phức tạp, sau đó bạn có thể cần phải làm cho nó picklable (đó là khá dễ dàng; các pickletài liệu giải thích nó), và sau đó lúc tồi tệ nhất wrapper ngu ngốc của bạn là def wrapper(obj, *args): return obj.wrapper(*args).
abarnert

32

Nhiều luồng có thể tồn tại trong một tiến trình duy nhất. Các luồng thuộc cùng một tiến trình chia sẻ cùng một vùng nhớ (có thể đọc và ghi vào các biến rất giống nhau và có thể can thiệp lẫn nhau). Ngược lại, các quá trình khác nhau sống trong các vùng nhớ khác nhau và mỗi quá trình có các biến riêng. Để giao tiếp, các quy trình phải sử dụng các kênh khác (tệp, đường ống hoặc ổ cắm).

Nếu bạn muốn song song hóa một tính toán, có lẽ bạn sẽ cần đa luồng, bởi vì bạn có thể muốn các luồng hợp tác trên cùng một bộ nhớ.

Nói về hiệu năng, các luồng được tạo và quản lý nhanh hơn các quy trình (vì HĐH không cần phân bổ một vùng bộ nhớ ảo hoàn toàn mới) và giao tiếp giữa các luồng thường nhanh hơn giao tiếp giữa các quá trình. Nhưng chủ đề khó hơn để lập trình. Các chủ đề có thể can thiệp lẫn nhau và có thể ghi vào bộ nhớ của nhau, nhưng cách điều này xảy ra không phải lúc nào cũng rõ ràng (do một số yếu tố, chủ yếu là sắp xếp lại hướng dẫn và bộ nhớ đệm), và do đó bạn sẽ cần các nguyên tắc đồng bộ hóa để kiểm soát truy cập để các biến của bạn.


12
Điều này thiếu một số thông tin rất quan trọng về GIL, khiến nó bị sai lệch.
bỏ qua

1
@ mr2ert: Vâng, đó là thông tin rất quan trọng trong một tóm tắt. :) Nhưng nó phức tạp hơn thế một chút, đó là lý do tại sao tôi viết một câu trả lời riêng.
bỏ qua

2
Tôi nghĩ rằng tôi đã nhận xét rằng @abarnert là đúng, và tôi đã quên GIL khi trả lời ở đây. Vì vậy, câu trả lời này là sai, bạn không nên nâng cao nó.
Giulio Franco

6
Tôi đã đánh giá thấp câu trả lời này bởi vì nó vẫn không trả lời gì cả về sự khác biệt giữa Python threadingmultiprocessing.
Antti Haapala

Tôi đã đọc rằng có một GIL cho mọi quy trình. Nhưng có phải tất cả các quy trình đều sử dụng cùng một trình thông dịch python hoặc có trình thông dịch riêng cho mỗi luồng không?
biến

3

Tôi tin rằng liên kết này trả lời câu hỏi của bạn một cách thanh lịch.

Nói ngắn gọn, nếu một trong những vấn đề phụ của bạn phải chờ trong khi một vấn đề khác kết thúc, thì đa luồng là tốt (chẳng hạn trong các hoạt động nặng của I / O); ngược lại, nếu các vấn đề phụ của bạn thực sự có thể xảy ra cùng một lúc, thì đa xử lý được đề xuất. Tuy nhiên, bạn sẽ không tạo ra nhiều quy trình hơn số lõi của mình.


3

Báo giá tài liệu Python

Tôi đã nhấn mạnh các trích dẫn tài liệu Python chính về Process vs Chủ đề và GIL tại: Khóa trình thông dịch toàn cầu (GIL) trong CPython là gì?

Quá trình so với thí nghiệm luồng

Tôi đã làm một chút điểm chuẩn để thể hiện sự khác biệt một cách cụ thể hơn.

Trong điểm chuẩn, tôi đã định thời gian cho CPU và IO bị ràng buộc làm việc với số lượng luồng khác nhau trên 8 CPU siêu phân luồng . Công việc được cung cấp cho mỗi luồng luôn giống nhau, sao cho nhiều luồng hơn có nghĩa là tổng số công việc được cung cấp nhiều hơn.

Kết quả là:

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

Lô dữ liệu .

Kết luận:

  • đối với công việc bị ràng buộc bởi CPU, đa xử lý luôn nhanh hơn, có lẽ là do GIL

  • cho công việc ràng buộc IO. cả hai đều có cùng tốc độ

  • các luồng chỉ có tỷ lệ lên tới khoảng 4x thay vì 8 x như mong đợi vì tôi đang sử dụng máy siêu phân luồng 8.

    Trái ngược với công việc giới hạn CPU C POSIX đạt tốc độ tăng tốc dự kiến ​​là 8: 'thực', 'người dùng' và 'sys' nghĩa là gì trong đầu ra của thời gian (1)?

    TODO: Tôi không biết lý do cho việc này, phải có sự thiếu hiệu quả của Python khác.

Mã kiểm tra:

#!/usr/bin/env python3

import multiprocessing
import threading
import time
import sys

def cpu_func(result, niters):
    '''
    A useless CPU bound function.
    '''
    for i in range(niters):
        result = (result * result * i + 2 * result * i * i + 3) % 10000000
    return result

class CpuThread(threading.Thread):
    def __init__(self, niters):
        super().__init__()
        self.niters = niters
        self.result = 1
    def run(self):
        self.result = cpu_func(self.result, self.niters)

class CpuProcess(multiprocessing.Process):
    def __init__(self, niters):
        super().__init__()
        self.niters = niters
        self.result = 1
    def run(self):
        self.result = cpu_func(self.result, self.niters)

class IoThread(threading.Thread):
    def __init__(self, sleep):
        super().__init__()
        self.sleep = sleep
        self.result = self.sleep
    def run(self):
        time.sleep(self.sleep)

class IoProcess(multiprocessing.Process):
    def __init__(self, sleep):
        super().__init__()
        self.sleep = sleep
        self.result = self.sleep
    def run(self):
        time.sleep(self.sleep)

if __name__ == '__main__':
    cpu_n_iters = int(sys.argv[1])
    sleep = 1
    cpu_count = multiprocessing.cpu_count()
    input_params = [
        (CpuThread, cpu_n_iters),
        (CpuProcess, cpu_n_iters),
        (IoThread, sleep),
        (IoProcess, sleep),
    ]
    header = ['nthreads']
    for thread_class, _ in input_params:
        header.append(thread_class.__name__)
    print(' '.join(header))
    for nthreads in range(1, 2 * cpu_count):
        results = [nthreads]
        for thread_class, work_size in input_params:
            start_time = time.time()
            threads = []
            for i in range(nthreads):
                thread = thread_class(work_size)
                threads.append(thread)
                thread.start()
            for i, thread in enumerate(threads):
                thread.join()
            results.append(time.time() - start_time)
        print(' '.join('{:.6e}'.format(result) for result in results))

GitHub ngược dòng + vẽ mã trên cùng một thư mục .

Đã thử nghiệm trên Ubuntu 18.10, Python 3.6.7, trên máy tính xách tay Lenovo ThinkPad P51 có CPU: CPU Intel Core i7-7820HQ (4 lõi / 8 luồng), RAM: 2x Samsung M471A2K43BB1-CRC (2x 16GiB), SSD: Samsung MZVLB512 000L7 (3.000 MB / giây).

Hình dung những chủ đề đang chạy tại một thời điểm nhất định

Bài đăng này https://rohanvarma.me/GIL/ đã dạy tôi rằng bạn có thể chạy một cuộc gọi lại bất cứ khi nào một chủ đề được lên lịch với target=đối sốthreading.Thread và tương tự cho multiprocessing.Process.

Điều này cho phép chúng ta xem chính xác chủ đề nào chạy mỗi lần. Khi điều này được thực hiện, chúng ta sẽ thấy một cái gì đó như (Tôi đã tạo ra biểu đồ cụ thể này):

            +--------------------------------------+
            + Active threads / processes           +
+-----------+--------------------------------------+
|Thread   1 |********     ************             |
|         2 |        *****            *************|
+-----------+--------------------------------------+
|Process  1 |***  ************** ******  ****      |
|         2 |** **** ****** ** ********* **********|
+-----------+--------------------------------------+
            + Time -->                             +
            +--------------------------------------+

Điều đó sẽ cho thấy rằng:

  • chủ đề được nối tiếp hoàn toàn bởi GIL
  • các quá trình có thể chạy song song

1

Đây là một số dữ liệu hiệu suất cho python 2.6.x gọi để đặt câu hỏi về khái niệm rằng luồng là hiệu suất cao hơn mà đa xử lý trong các kịch bản ràng buộc IO. Những kết quả này là từ Hệ thống IBM x3650 M4 BD 40 bộ xử lý.

IO-Bound Treatment: Process Pool hoạt động tốt hơn Thread Pool

>>> do_work(50, 300, 'thread','fileio')
do_work function took 455.752 ms

>>> do_work(50, 300, 'process','fileio')
do_work function took 319.279 ms

Xử lý giới hạn CPU: Nhóm quy trình thực hiện tốt hơn Nhóm xử lý

>>> do_work(50, 2000, 'thread','square')
do_work function took 338.309 ms

>>> do_work(50, 2000, 'process','square')
do_work function took 287.488 ms

Đây không phải là các bài kiểm tra nghiêm ngặt, nhưng chúng cho tôi biết rằng đa xử lý không hoàn toàn không phù hợp so với phân luồng.

Mã được sử dụng trong bảng điều khiển python tương tác cho các thử nghiệm trên

from multiprocessing import Pool
from multiprocessing.pool import ThreadPool
import time
import sys
import os
from glob import glob

text_for_test = str(range(1,100000))

def fileio(i):
 try :
  os.remove(glob('./test/test-*'))
 except : 
  pass
 f=open('./test/test-'+str(i),'a')
 f.write(text_for_test)
 f.close()
 f=open('./test/test-'+str(i),'r')
 text = f.read()
 f.close()


def square(i):
 return i*i

def timing(f):
 def wrap(*args):
  time1 = time.time()
  ret = f(*args)
  time2 = time.time()
  print '%s function took %0.3f ms' % (f.func_name, (time2-time1)*1000.0)
  return ret
 return wrap

result = None

@timing
def do_work(process_count, items, process_type, method) :
 pool = None
 if process_type == 'process' :
  pool = Pool(processes=process_count)
 else :
  pool = ThreadPool(processes=process_count)
 if method == 'square' : 
  multiple_results = [pool.apply_async(square,(a,)) for a in range(1,items)]
  result = [res.get()  for res in multiple_results]
 else :
  multiple_results = [pool.apply_async(fileio,(a,)) for a in range(1,items)]
  result = [res.get()  for res in multiple_results]


do_work(50, 300, 'thread','fileio')
do_work(50, 300, 'process','fileio')

do_work(50, 2000, 'thread','square')
do_work(50, 2000, 'process','square')

Tôi đã sử dụng mã của bạn (đã xóa phần toàn cầu ) và đã tìm thấy kết quả thú vị này với Python 2.6.6:>>> do_work(50, 300, 'thread', 'fileio') --> 237.557 ms >>> do_work(50, 300, 'process', 'fileio') --> 323.963 ms >>> do_work(50, 2000, 'thread', 'square') --> 232.082 ms >>> do_work(50, 2000, 'process', 'square') --> 282.785 ms
Alan Garrido

-5

Chà, hầu hết câu hỏi được trả lời bởi Giulio Franco. Tôi sẽ giải thích thêm về vấn đề nhà sản xuất tiêu dùng, mà tôi cho rằng sẽ đưa bạn đi đúng hướng cho giải pháp của bạn để sử dụng một ứng dụng đa luồng.

fill_count = Semaphore(0) # items produced
empty_count = Semaphore(BUFFER_SIZE) # remaining space
buffer = Buffer()

def producer(fill_count, empty_count, buffer):
    while True:
        item = produceItem()
        empty_count.down();
        buffer.push(item)
        fill_count.up()

def consumer(fill_count, empty_count, buffer):
    while True:
        fill_count.down()
        item = buffer.pop()
        empty_count.up()
        consume_item(item)

Bạn có thể đọc thêm về các nguyên thủy đồng bộ hóa từ:

 http://linux.die.net/man/7/sem_overview
 http://docs.python.org/2/library/threading.html

Mã giả ở trên. Tôi cho rằng bạn nên tìm kiếm vấn đề nhà sản xuất-người tiêu dùng để có thêm tài liệu tham khảo.


xin lỗi innosam, nhưng điều này có vẻ như C ++ với tôi? cảm ơn vì các liên kết :)
lucacerone

Trên thực tế, các ý tưởng đằng sau đa xử lý và đa luồng là độc lập ngôn ngữ. Giải pháp sẽ tương tự như mã trên.
innosam

2
Đây không phải là C ++; đó là mã giả (hoặc mã của ngôn ngữ được gõ động nhiều nhất với cú pháp giống như C. Điều đó nói rằng, tôi nghĩ rằng việc viết mã giả giống như Python để dạy người dùng Python là điều hữu ích hơn (đặc biệt là vì psuedocode giống như Python thường hóa ra là mã có thể chạy được, hoặc ít nhất là gần với mã này, điều này hiếm khi đúng với mã giả giống như C)
abarnert

Tôi đã viết lại nó dưới dạng mã giả giống Python (cũng sử dụng OO và truyền tham số thay vì sử dụng các đối tượng toàn cục); cảm thấy thoải mái để hoàn nguyên nếu bạn nghĩ rằng điều đó làm cho mọi thứ ít rõ ràng hơn.
abarnert

Ngoài ra, điều đáng chú ý là stdlib Python có một hàng đợi được đồng bộ hóa được xây dựng trong đó bao bọc tất cả các chi tiết này, và các API trừu tượng của chuỗi xử lý và xử lý các nhóm trừu tượng hơn nữa. Điều này chắc chắn đáng để hiểu cách các hàng đợi được đồng bộ hóa hoạt động dưới vỏ bọc, nhưng bạn sẽ hiếm khi cần phải tự viết một cái.
abarnert
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.