Logo Zephyrnet

Giới thiệu về Đa luồng và Đa xử lý trong Python – KDnuggets

Ngày:

Giới thiệu về Đa luồng và Đa xử lý trong Python
Hình ảnh của Tác giả
 

Hướng dẫn này sẽ thảo luận về việc tận dụng khả năng của Python để thực hiện các tác vụ đa luồng và đa chương trình. Họ cung cấp một cổng để thực hiện các hoạt động đồng thời trong một quy trình hoặc trên nhiều quy trình. Thực hiện song song và đồng thời làm tăng tốc độ và hiệu quả của hệ thống. Sau khi thảo luận những kiến ​​thức cơ bản về đa luồng và đa chương trình, chúng ta cũng sẽ thảo luận về cách triển khai thực tế của chúng bằng thư viện Python. Trước tiên chúng ta hãy thảo luận ngắn gọn về lợi ích của các hệ thống song song.

  1. Cải thiện hiệu suất: Với khả năng thực hiện đồng thời các tác vụ, chúng tôi có thể giảm thời gian thực hiện và cải thiện hiệu suất tổng thể của hệ thống.
  2. Khả năng mở rộng: Chúng ta có thể chia một tác vụ lớn thành nhiều tác vụ phụ nhỏ hơn và gán một lõi hoặc luồng riêng cho chúng để chúng thực thi độc lập. Nó có thể hữu ích trong các hệ thống quy mô lớn.
  3. Hoạt động I/O hiệu quả: Với sự trợ giúp của tính đồng thời, CPU không phải đợi một tiến trình hoàn thành các hoạt động I/O của nó. CPU có thể ngay lập tức bắt đầu thực hiện quy trình sau cho đến khi quy trình trước đó bận với I/O của nó.
  4. Tối ưu hóa tài nguyên: Bằng cách phân chia tài nguyên, chúng ta có thể ngăn không cho một tiến trình chiếm hết tài nguyên. Điều này có thể tránh được vấn đề Nạn đói cho các tiến trình nhỏ hơn.

 

Giới thiệu về Đa luồng và Đa xử lý trong Python
Lợi ích của tính toán song song | Hình ảnh của tác giả
 

Đây là một số lý do phổ biến khiến bạn yêu cầu thực thi đồng thời hoặc song song. Bây giờ, hãy quay lại các chủ đề chính, tức là Đa luồng và Đa chương trình, đồng thời thảo luận về những khác biệt chính của chúng.

Đa luồng là một trong những cách để đạt được tính song song trong một tiến trình đơn lẻ và có thể thực hiện các tác vụ đồng thời. Nhiều luồng có thể được tạo bên trong một tiến trình và thực hiện song song các tác vụ nhỏ hơn trong tiến trình đó. 

Các luồng hiện diện bên trong một tiến trình chia sẻ một không gian bộ nhớ chung, nhưng dấu vết ngăn xếp và các thanh ghi của chúng là riêng biệt. Chúng ít tốn kém về mặt tính toán hơn nhờ bộ nhớ dùng chung này.

 

Giới thiệu về Đa luồng và Đa xử lý trong Python
Env đơn luồng và đa luồng. | Hình ảnh bởi GeekForGeek
 

Đa luồng chủ yếu được sử dụng để thực hiện các thao tác I/O, tức là nếu một phần nào đó của chương trình bận trong các thao tác I/O thì chương trình còn lại có thể đáp ứng. Tuy nhiên, trong quá trình triển khai Python, đa luồng không thể đạt được tính song song thực sự do Khóa phiên dịch toàn cầu (GIL).

Nói tóm lại, GIL là một khóa mutex cho phép chỉ một luồng tại một thời điểm tương tác với mã byte Python, tức là ngay cả ở chế độ đa luồng, mỗi lần chỉ có một luồng có thể thực thi mã byte.

Nó được thực hiện để duy trì sự an toàn của luồng trong CPython, nhưng điều này hạn chế lợi ích về hiệu suất của đa luồng. Để giải quyết vấn đề này, python có một thư viện đa xử lý riêng biệt mà chúng ta sẽ thảo luận sau.

Chủ đề Daemon là gì?

Các luồng liên tục chạy ở chế độ nền được gọi là các luồng quỷ. Công việc chính của họ là hỗ trợ luồng chính hoặc các luồng không phải daemon. Luồng daemon không chặn luồng chính thực thi và thậm chí vẫn tiếp tục chạy nếu nó đã hoàn thành việc thực thi.

Trong Python, các luồng daemon chủ yếu được sử dụng làm trình thu gom rác. Nó sẽ phá hủy tất cả các đối tượng vô dụng và giải phóng bộ nhớ theo mặc định để luồng chính có thể được sử dụng và thực thi đúng cách.

Đa xử lý được sử dụng để thực hiện việc thực hiện song song nhiều quy trình. Nó giúp chúng tôi đạt được tính song song thực sự khi chúng tôi thực hiện đồng thời các quy trình riêng biệt, có không gian bộ nhớ riêng. Nó sử dụng các lõi CPU riêng biệt và cũng hữu ích trong việc thực hiện giao tiếp giữa các tiến trình để trao đổi dữ liệu giữa nhiều tiến trình.

Đa xử lý tốn kém hơn về mặt tính toán so với đa luồng vì chúng tôi không sử dụng không gian bộ nhớ dùng chung. Tuy nhiên, nó cho phép chúng tôi thực thi độc lập và khắc phục các hạn chế của Global Interpreter Lock.

 

Giới thiệu về Đa luồng và Đa xử lý trong Python
Môi trường đa xử lý | Hình ảnh bởi GeekForGeek
 

Hình trên thể hiện một môi trường đa xử lý trong đó một quy trình chính tạo ra hai quy trình riêng biệt và phân công công việc riêng biệt cho chúng.

Đã đến lúc triển khai một ví dụ cơ bản về đa luồng bằng Python. Python có một mô-đun sẵn có threading được sử dụng để thực hiện đa luồng.

  1. Nhập thư viện:
import threading
import os

 

  1. Chức năng tính toán bình phương:

Đây là một hàm đơn giản được sử dụng để tìm bình phương của các số. Một danh sách các số được đưa ra làm đầu vào và nó xuất ra bình phương của mỗi số trong danh sách cùng với tên của luồng được sử dụng và ID tiến trình được liên kết với luồng đó.

def calculate_squares(numbers):
    for num in numbers:
        square = num * num
        print(
            f"Square of the number {num} is {square} | Thread Name {threading.current_thread().name} | PID of the process {os.getpid()}"
        )

 

  1. Chức năng chính:

Chúng ta có một danh sách các số và chúng ta sẽ chia danh sách đó cho đều và đặt tên chúng là fisrt_half và second_half tương ứng. Bây giờ chúng ta sẽ chỉ định hai chủ đề riêng biệt t1t2 vào các danh sách này.

Thread Hàm tạo một luồng mới, luồng này nhận vào một hàm có danh sách các đối số cho hàm đó. Bạn cũng có thể gán tên riêng cho một chủ đề.

.start() chức năng sẽ bắt đầu thực hiện các chủ đề này và .join() Hàm sẽ chặn việc thực thi luồng chính cho đến khi luồng đã cho không được thực thi hoàn toàn.

if __name__ == "__main__":
    numbers = [1, 2, 3, 4, 5, 6, 7, 8]
    half = len(numbers) // 2
    first_half = numbers[:half]
    second_half = numbers[half:]

    t1 = threading.Thread(target=calculate_squares, name="t1", args=(first_half,))
    t2 = threading.Thread(target=calculate_squares, name="t2", args=(second_half,))

    t1.start()
    t2.start()

    t1.join()
    t2.join()

 

Đầu ra:

Square of the number 1 is 1 | Thread Name t1 | PID of the process 345
Square of the number 2 is 4 | Thread Name t1 | PID of the process 345
Square of the number 5 is 25 | Thread Name t2 | PID of the process 345
Square of the number 3 is 9 | Thread Name t1 | PID of the process 345
Square of the number 6 is 36 | Thread Name t2 | PID of the process 345
Square of the number 4 is 16 | Thread Name t1 | PID of the process 345
Square of the number 7 is 49 | Thread Name t2 | PID of the process 345
Square of the number 8 is 64 | Thread Name t2 | PID of the process 345

 

Lưu ý: Tất cả các chủ đề được tạo ở trên đều là các chủ đề không phải daemon. Để tạo một chuỗi daemon, bạn cần viết t1.setDaemon(True) để làm sợi chỉ t1 một chủ đề daemon.

 

Bây giờ, chúng ta sẽ hiểu đầu ra được tạo bởi đoạn mã trên. Chúng ta có thể quan sát rằng ID tiến trình (tức là PID) sẽ giữ nguyên cho cả hai luồng, điều đó có nghĩa là hai luồng này là một phần của cùng một tiến trình.

Bạn cũng có thể quan sát rằng đầu ra không được tạo tuần tự. Trong dòng đầu tiên, bạn sẽ thấy đầu ra được tạo bởi thread1, sau đó ở dòng thứ 3, đầu ra được tạo bởi thread2, sau đó lại được tạo bởi thread1 ở dòng thứ tư. Điều này biểu thị rõ ràng rằng các luồng này hoạt động đồng thời với nhau.

Đồng thời không có nghĩa là hai luồng này được thực thi song song, vì mỗi lần chỉ có một luồng được thực thi. Nó không làm giảm thời gian thực hiện. Phải mất thời gian tương tự như thực hiện tuần tự. CPU bắt đầu thực thi một luồng nhưng bỏ dở giữa chừng và chuyển sang một luồng khác, sau một thời gian, quay trở lại luồng chính và bắt đầu thực thi từ cùng một điểm mà nó đã rời đi lần trước.

Tôi hy vọng bạn có hiểu biết cơ bản về đa luồng cũng như cách triển khai và những hạn chế của nó. Bây giờ là lúc tìm hiểu về cách triển khai đa xử lý và cách chúng ta có thể khắc phục những hạn chế đó. 

Chúng ta sẽ làm theo ví dụ tương tự, nhưng thay vì tạo hai luồng riêng biệt, chúng ta sẽ tạo hai quy trình độc lập và thảo luận về các quan sát.

  1. Nhập thư viện:
from multiprocessing import Process
import os

 

Chúng tôi sẽ sử dụng multiprocessing mô-đun để tạo ra các quy trình độc lập. 

  1. Chức năng tính toán bình phương:

Chức năng đó sẽ vẫn như cũ. Chúng tôi vừa xóa câu lệnh in của thông tin luồng.

def calculate_squares(numbers):
    for num in numbers:
        square = num * num
        print(
            f"Square of the number {num} is {square} | PID of the process {os.getpid()}"
        )

 

  1. Chức năng chính:

Có một vài sửa đổi trong chức năng chính. Chúng tôi vừa tạo một quy trình riêng thay vì một chuỗi.

if __name__ == "__main__":
    numbers = [1, 2, 3, 4, 5, 6, 7, 8]
    half = len(numbers) // 2
    first_half = numbers[:half]
    second_half = numbers[half:]

    p1 = Process(target=calculate_squares, args=(first_half,))
    p2 = Process(target=calculate_squares, args=(second_half,))

    p1.start()
    p2.start()

    p1.join()
    p2.join()

 

Đầu ra:

Square of the number 1 is 1 | PID of the process 1125
Square of the number 2 is 4 | PID of the process 1125
Square of the number 3 is 9 | PID of the process 1125
Square of the number 4 is 16 | PID of the process 1125
Square of the number 5 is 25 | PID of the process 1126
Square of the number 6 is 36 | PID of the process 1126
Square of the number 7 is 49 | PID of the process 1126
Square of the number 8 is 64 | PID of the process 1126

 

Chúng tôi đã quan sát thấy rằng có một quy trình riêng biệt thực thi từng danh sách. Cả hai đều có ID tiến trình khác nhau. Để kiểm tra xem các quy trình của chúng tôi đã được thực thi song song hay chưa, chúng tôi cần tạo một môi trường riêng biệt mà chúng tôi sẽ thảo luận bên dưới.

Tính toán thời gian chạy có và không có đa xử lý

Để kiểm tra xem chúng tôi có đạt được sự song song thực sự hay không, chúng tôi sẽ tính toán thời gian chạy của thuật toán khi có và không có đa xử lý.

Để làm được điều này, chúng tôi sẽ yêu cầu một danh sách số nguyên phong phú chứa hơn 10^6 số nguyên. Chúng ta có thể tạo một danh sách bằng cách sử dụng random thư viện. Chúng tôi sẽ sử dụng time mô-đun Python để tính toán thời gian chạy. Dưới đây là việc thực hiện cho việc này. Mã này tự giải thích, mặc dù bạn luôn có thể xem nhận xét mã.

from multiprocessing import Process
import os
import time
import random

def calculate_squares(numbers):
    for num in numbers:
        square = num * num

if __name__ == "__main__":
    numbers = [
        random.randrange(1, 50, 1) for i in range(10000000)
    ]  # Creating a random list of integers having size 10^7.
    half = len(numbers) // 2
    first_half = numbers[:half]
    second_half = numbers[half:]

    # ----------------- Creating Single Process Environment ------------------------#

    start_time = time.time()  # Start time without multiprocessing

    p1 = Process(
        target=calculate_squares, args=(numbers,)
    )  # Single process P1 is executing all list
    p1.start()
    p1.join()

    end_time = time.time()  # End time without multiprocessing
    print(f"Execution Time Without Multiprocessing: {(end_time-start_time)*10**3}ms")

    # ----------------- Creating Multi Process Environment ------------------------#

    start_time = time.time()  # Start time with multiprocessing

    p2 = Process(target=calculate_squares, args=(first_half,))
    p3 = Process(target=calculate_squares, args=(second_half,))

    p2.start()
    p3.start()

    p2.join()
    p3.join()

    end_time = time.time()  # End time with multiprocessing
    print(f"Execution Time With Multiprocessing: {(end_time-start_time)*10**3}ms")

 

Đầu ra:

Execution Time Without Multiprocessing: 619.8039054870605ms
Execution Time With Multiprocessing: 321.70287895202637ms

 

Bạn có thể nhận thấy rằng thời gian thực hiện đa xử lý gần bằng một nửa so với khi không thực hiện đa xử lý. Điều này cho thấy hai quá trình này được thực thi đồng thời tại một thời điểm và thể hiện hành vi song song thực sự.

Bạn cũng có thể đọc bài viết này Tuần tự vs Đồng thời vs Song song từ Phương tiện, điều này sẽ giúp bạn hiểu được sự khác biệt cơ bản giữa các quy trình Tuần tự, Đồng thời và Song song này.
 
 

Garg Aryan là một B.Tech. Sinh viên khoa Điện, hiện đang học năm cuối đại học. Mối quan tâm của anh ấy là lĩnh vực Phát triển web và Học máy. Anh ấy đã theo đuổi sở thích này và háo hức làm việc nhiều hơn theo những hướng này.

tại chỗ_img

Tin tức mới nhất

tại chỗ_img