Đa luồng và Đa xử lý là hai cách phổ biến nhất để đạt được sự đồng thời và song song, tuy nhiên, không nhiều nhà phát triển hiểu được sự khác biệt giữa chúng và không chọn được hiệu quả để sử dụng khi nào.
Trong bài viết này, chúng ta sẽ thảo luận về sự khác biệt giữa Đa luồng và Đa xử lý cũng như cách quyết định những gì sẽ sử dụng và cách triển khai nó trong Python.
Một luồng là một luồng thực thi độc lập. Về cơ bản, nó có thể được xem như một thành phần riêng lẻ nhẹ của một quy trình, có thể chạy song song. Phân luồng là một tính năng thường được cung cấp bởi hệ điều hành. Có thể có nhiều luồng trong một tiến trình, chia sẻ cùng một không gian bộ nhớ, có nghĩa là chúng chia sẻ mã được thực thi và các biến được khai báo trong chương trình với nhau.
Để hiểu rõ hơn điều này, chúng ta hãy xem xét một ví dụ về các chương trình đang chạy trên máy tính xách tay của bạn ngay bây giờ. Có thể bạn đang đọc bài viết này với nhiều tab đang mở trong trình duyệt của mình. Trong khi đó, bạn có ứng dụng Spotify trên máy tính để bàn đang mở để nghe nhạc. Giờ đây, trình duyệt và ứng dụng Spotify dành cho máy tính để bàn giống như hai quy trình riêng biệt có thể sử dụng một số quy trình hoặc chuỗi để đạt được sự song song. Vì vậy, các tab khác nhau trong trình duyệt của bạn có thể được chạy trong các chuỗi khác nhau. Tương tự, Spotify có thể phát nhạc bằng một chuỗi và sử dụng một chuỗi khác để tải xuống bài hát yêu thích của bạn từ internet và sử dụng một chuỗi thứ ba để hiển thị giao diện người dùng. Và điều này được gọi là Đa luồng.
Đa luồng, như tên cho thấy, là một nhiệm vụ hoặc một hoạt động có thể thực hiện nhiều luồng cùng một lúc. Đây là một kỹ thuật phổ biến giúp sắp xếp hợp lý nhiều tác vụ liên tiếp nhanh chóng cùng một lúc và tạo điều kiện chia sẻ tài nguyên nhanh chóng và dễ dàng giữa nhiều luồng với luồng chính.
Hình ảnh sau giải thích Đa luồng trong Python:
Python là một ngôn ngữ tuyến tính, nhưng chúng ta có thể sử dụng mô-đun Threading Python để hiểu và triển khai khái niệm Đa luồng trong Python. Mô-đun phân luồng cung cấp một API tinh vi để dễ dàng tạo ra nhiều luồng có thể được sử dụng khi cần nhiều sức mạnh xử lý hơn.
Nó có thể được sử dụng như hình dưới đây:
import threading from queue import Queue import time def testThread(num): print num if __name__ == '__main__': for i in range(5): t = threading.Thread(target=testThread, arg=(i,)) t.start()
Trong đoạn mã trên, target
được sử dụng làm đối tượng có thể gọi, args
để chuyển các tham số cho hàm và start
bắt đầu chuỗi.
Bây giờ, ở đây có một thứ thú vị - ổ khóa.
Thường có những trường hợp trong lập trình mà bạn muốn các luồng của mình có thể sửa đổi hoặc sử dụng các biến phổ biến cho các luồng. Tuy nhiên, để làm điều này, bạn sẽ phải sử dụng một thứ được gọi là Khóa hoặc Khóa thông dịch viên toàn cầu (GIL) trong Python.
Từ Python
Trong CPython, khóa thông dịch viên toàn cầu , hoặc GIL , là một mutex bảo vệ quyền truy cập vào các đối tượng Python, ngăn nhiều luồng thực thi các mã byte Python cùng một lúc. Khóa này là cần thiết chủ yếu vì quản lý bộ nhớ của CPython không an toàn theo luồng.
Ở cấp thông dịch, Python về cơ bản tuần tự hóa các hướng dẫn. Để bất kỳ luồng nào có thể chạy bất kỳ chức năng nào, trước tiên nó phải nhận được một khóa toàn cục. Bởi vì chỉ một luồng có thể đạt được khóa đó tại một thời điểm, trình thông dịch cuối cùng phải thực hiện các lệnh một cách tuần tự. Kiến trúc này giúp quản lý bộ nhớ an toàn theo luồng, nhưng nó không thể sử dụng nhiều lõi CPU.
Nói một cách đơn giản, bất cứ khi nào một hàm muốn sử dụng hoặc sửa đổi một biến, nó sẽ khóa biến đó lại để nếu bất kỳ hàm nào khác muốn sử dụng hoặc sửa đổi biến cụ thể đó, nó sẽ phải đợi cho đến khi biến đó được mở khóa.
Hãy xem xét hai hàm mà mỗi hàm lặp lại một biến. Bạn có thể sử dụng khóa để đảm bảo rằng một hàm có thể đọc biến, chạy tính toán và ghi lại vào nó trước khi một hàm khác có thể, để chúng ta có thể tránh bị hỏng dữ liệu.
Phân luồng bằng Python hữu ích hơn cho các hoạt động I / O hoặc tác vụ liên kết mạng, chẳng hạn như chạy tập lệnh, chẳng hạn như trong trường hợp quét web hơn là các tác vụ có thể sử dụng nhiều CPU. Một ví dụ khác là Tensorflow , sử dụng một nhóm luồng để chuyển đổi dữ liệu song song.
Ngoài các ứng dụng này, Giao diện người dùng đồ họa (GUI) luôn sử dụng Đa luồng để làm cho các ứng dụng phản hồi và tương tác. Một ví dụ phổ biến có thể là một chương trình chỉnh sửa văn bản mà ngay khi người dùng nhập văn bản, nó sẽ hiển thị trên màn hình. Ở đây một luồng xử lý đầu vào của người dùng trong khi luồng kia xử lý tác vụ hiển thị nó. Chúng tôi có thể thêm nhiều chủ đề hơn để có nhiều chức năng hơn như kiểm tra chính tả, tự động hoàn thành, v.v.
Bây giờ, đã thảo luận chi tiết về các luồng, chúng ta hãy chuyển sang các quy trình.
Một tiến trình chỉ đơn giản là một phiên bản của chương trình máy tính đang được thực thi. Mỗi tiến trình có không gian bộ nhớ riêng được sử dụng để lưu các lệnh đang được chạy và bất kỳ dữ liệu nào mà nó cần truy cập hoặc lưu trữ để thực thi mã. Do đó, quá trình tạo ra một quá trình tốn nhiều thời gian và chậm hơn so với một chuỗi.
Như chúng ta đã thảo luận trước đó, khi chúng ta đang chạy nhiều ứng dụng trên máy tính để bàn của mình, mỗi ứng dụng là một quá trình và khi nào thực hiện các quá trình này cùng một lúc, nó được gọi là Đa xử lý.
Đa xử lý là khả năng của một bộ xử lý để thực hiện đồng thời một số tác vụ không liên quan. Nó cho phép bạn tạo các chương trình có thể chạy đồng thời, bỏ qua Khóa thông dịch toàn cầu (GIL) và sử dụng toàn bộ lõi CPU để thực thi hiệu quả các tác vụ.
Mặc dù khái niệm Đa xử lý về cơ bản khác với Đa luồng, nhưng cú pháp hoặc cách sử dụng của chúng trong Python là khá giống nhau. Tương tự như mô-đun Luồng, chúng ta có một mô-đun Đa xử lý bằng Python giúp tạo ra các quy trình khác nhau, trong đó mỗi quy trình có trình thông dịch Python và GIL của riêng nó.
Vì các tiến trình không chia sẻ cùng một bộ nhớ, chúng không thể sửa đổi đồng thời cùng một bộ nhớ, giúp chúng ta tránh khỏi nguy cơ gặp bế tắc hoặc nguy cơ hỏng dữ liệu.
Nó có thể được sử dụng như hình dưới đây:
import multiprocessing def spawn(num): print(num) if __name__ == '__main__': for i in range(5): p = multiprocessing.Process(target=spawn, args=(i,)) p.start() p.join() # this line allows you to wait for processes
Như chúng ta đã thảo luận trước đó, Mutliprocessing là một lựa chọn khôn ngoan hơn trong trường hợp các tác vụ có nhiều CPU và không có bất kỳ hoạt động I / O hoặc tương tác người dùng nào.
Dưới đây là một số điểm để tóm tắt sự khác biệt, ưu điểm và nhược điểm của Đa xử lý và Đa luồng:
Chúng ta có thể rút ra các kết luận sau từ cuộc thảo luận này:
Bây giờ bạn đã hiểu cách hoạt động của Đa xử lý và Đa luồng trong Python và cách chúng so sánh, bạn có thể viết mã một cách hiệu quả và áp dụng hai cách tiếp cận trong nhiều trường hợp khác nhau.
Tôi hy vọng bạn thấy bài viết này hữu ích. Hãy đọc tiếp!