Hạn chế chính của luồng với Python là gì?

Trong hướng dẫn này, chúng tôi sẽ chỉ cho bạn cách đạt được tính song song trong mã của bạn bằng cách sử dụng các kỹ thuật đa luồng trong Python

"Song song", "đa luồng"— những thuật ngữ này có nghĩa là gì và chúng liên quan như thế nào?

  • Đồng thời là gì?
  • Sự khác biệt giữa đồng thời và song song là gì?
  • Sự khác biệt giữa các quy trình và chủ đề là gì?
  • Đa luồng trong Python là gì?
  • Khóa phiên dịch toàn cầu là gì?

Ở đây chúng tôi giả định rằng bạn đã biết các nguyên tắc cơ bản của Python, bao gồm các cấu trúc và chức năng cơ bản. Nếu bạn không quen với những điều này (hoặc háo hức tìm hiểu), hãy thử Kiến thức cơ bản về Python để phân tích dữ liệu – Dataquest của chúng tôi

Đồng thời là gì?

Trước khi đi vào chi tiết đa luồng, hãy so sánh lập trình đồng thời với lập trình tuần tự

Trong tính toán tuần tự, các thành phần của chương trình được thực hiện từng bước để tạo ra kết quả chính xác; . Do đó, bộ xử lý có thể chạy các thành phần ở các trạng thái khác nhau một cách độc lập và đồng thời. Ưu điểm chính của đồng thời là cải thiện thời gian chạy của chương trình, điều đó có nghĩa là vì bộ xử lý chạy các tác vụ độc lập đồng thời nên bộ xử lý cần ít thời gian hơn để chạy toàn bộ chương trình và hoàn thành tác vụ chính

Sự khác biệt giữa đồng thời và song song là gì?

Đồng thời là một điều kiện trong đó hai hoặc nhiều tác vụ có thể được bắt đầu và hoàn thành trong các khoảng thời gian chồng chéo trên một bộ xử lý và lõi. Tính song song là một điều kiện trong đó nhiều tác vụ hoặc các phần phân tán của một tác vụ chạy độc lập và đồng thời trên nhiều bộ xử lý. Vì vậy, không thể xử lý song song trên các máy có bộ xử lý đơn và lõi đơn

Hãy tưởng tượng hai hàng khách hàng; . Song song có nghĩa là hai nhân viên thu ngân đồng thời phục vụ hai hàng đợi của khách hàng

Sự khác biệt giữa các quy trình và chủ đề là gì?

Một quy trình là một chương trình đang thực thi với không gian địa chỉ, bộ nhớ, ngăn xếp dữ liệu, v.v. Hệ điều hành phân bổ tài nguyên cho các quy trình và quản lý việc thực thi của các quy trình bằng cách gán thời gian CPU cho các quy trình thực thi khác nhau, theo bất kỳ chiến lược lập lịch nào

Chủ đề tương tự như các quy trình. Tuy nhiên, chúng thực thi trong cùng một quy trình và chia sẻ cùng một bối cảnh. Do đó, việc chia sẻ thông tin hoặc giao tiếp với các luồng khác dễ truy cập hơn nếu chúng là các quy trình riêng biệt

Đa luồng trong Python

Máy ảo Python không phải là trình thông dịch an toàn cho luồng, nghĩa là trình thông dịch chỉ có thể thực thi một luồng tại bất kỳ thời điểm nào. Giới hạn này được thực thi bởi Khóa thông dịch viên toàn cầu Python (GIL), về cơ bản giới hạn một chuỗi Python chạy tại một thời điểm. Nói cách khác, GIL đảm bảo rằng chỉ có một luồng chạy trong cùng một quy trình tại cùng một thời điểm trên một bộ xử lý

Về cơ bản, luồng có thể không tăng tốc tất cả các tác vụ. Các tác vụ liên quan đến I/O dành nhiều thời gian chờ đợi các sự kiện bên ngoài có cơ hội tận dụng lợi thế của luồng tốt hơn so với các tác vụ liên quan đến CPU


GHI CHÚ

Python đi kèm với hai mô-đun tích hợp để triển khai các chương trình đa luồng, bao gồm mô-đun

import time
def calc_square(numbers):
    for n in numbers:
        print(f'\n{n} ^ 2 = {n*n}')
        time.sleep(0.1)

def calc_cube(numbers):
    for n in numbers:
        print(f'\n{n} ^ 3 = {n*n*n}')
        time.sleep(0.1)
6 và
import time
def calc_square(numbers):
    for n in numbers:
        print(f'\n{n} ^ 2 = {n*n}')
        time.sleep(0.1)

def calc_cube(numbers):
    for n in numbers:
        print(f'\n{n} ^ 3 = {n*n*n}')
        time.sleep(0.1)
7. Các mô-đun
import time
def calc_square(numbers):
    for n in numbers:
        print(f'\n{n} ^ 2 = {n*n}')
        time.sleep(0.1)

def calc_cube(numbers):
    for n in numbers:
        print(f'\n{n} ^ 3 = {n*n*n}')
        time.sleep(0.1)
6 và
import time
def calc_square(numbers):
    for n in numbers:
        print(f'\n{n} ^ 2 = {n*n}')
        time.sleep(0.1)

def calc_cube(numbers):
    for n in numbers:
        print(f'\n{n} ^ 3 = {n*n*n}')
        time.sleep(0.1)
7 cung cấp các tính năng hữu ích để tạo và quản lý chuỗi. Tuy nhiên, trong hướng dẫn này, chúng tôi sẽ tập trung vào mô-đun
import time
def calc_square(numbers):
    for n in numbers:
        print(f'\n{n} ^ 2 = {n*n}')
        time.sleep(0.1)

def calc_cube(numbers):
    for n in numbers:
        print(f'\n{n} ^ 3 = {n*n*n}')
        time.sleep(0.1)
7, đây là mô-đun cấp cao, được cải tiến nhiều để triển khai các chương trình đa luồng nghiêm trọng. Ngoài ra, Python cung cấp mô-đun
numbers = [2, 3, 5, 8]
start = time.time()
calc_square(numbers)
calc_cube(numbers)
end = time.time()

print('Execution Time: {}'.format(end-start))
1, cho phép chúng tôi tạo cấu trúc dữ liệu hàng đợi để trao đổi thông tin qua nhiều luồng một cách an toàn


Hãy bắt đầu với một ví dụ đơn giản để hiểu lợi ích của việc sử dụng lập trình đa luồng

Giả sử chúng ta có một danh sách các số nguyên và chúng ta sẽ tính bình phương và lập phương của mỗi số này rồi in chúng ra màn hình

Chương trình này bao gồm hai nhiệm vụ (chức năng) riêng biệt như sau

import time
def calc_square(numbers):
    for n in numbers:
        print(f'\n{n} ^ 2 = {n*n}')
        time.sleep(0.1)

def calc_cube(numbers):
    for n in numbers:
        print(f'\n{n} ^ 3 = {n*n*n}')
        time.sleep(0.1)

Đoạn mã trên thực hiện hai chức năng,

numbers = [2, 3, 5, 8]
start = time.time()
calc_square(numbers)
calc_cube(numbers)
end = time.time()

print('Execution Time: {}'.format(end-start))
2 và
numbers = [2, 3, 5, 8]
start = time.time()
calc_square(numbers)
calc_cube(numbers)
end = time.time()

print('Execution Time: {}'.format(end-start))
3. Hàm
numbers = [2, 3, 5, 8]
start = time.time()
calc_square(numbers)
calc_cube(numbers)
end = time.time()

print('Execution Time: {}'.format(end-start))
2 tính bình phương của mỗi số trong danh sách và hàm
numbers = [2, 3, 5, 8]
start = time.time()
calc_square(numbers)
calc_cube(numbers)
end = time.time()

print('Execution Time: {}'.format(end-start))
3 tính lập phương của mỗi số trong danh sách. Câu lệnh
numbers = [2, 3, 5, 8]
start = time.time()
calc_square(numbers)
calc_cube(numbers)
end = time.time()

print('Execution Time: {}'.format(end-start))
6 trong phần thân của các hàm tạm dừng việc thực thi mã trong 0. 1 giây ở cuối mỗi lần lặp. Chúng tôi đã thêm câu lệnh này vào các chức năng để làm cho CPU không hoạt động trong giây lát và mô phỏng tác vụ liên kết I/O. Trong các tình huống thực tế, tác vụ liên kết I/O có thể đợi thiết bị ngoại vi hoặc phản hồi của dịch vụ web

numbers = [2, 3, 5, 8]
start = time.time()
calc_square(numbers)
calc_cube(numbers)
end = time.time()

print('Execution Time: {}'.format(end-start))

Như bạn đã thấy, việc thực hiện tuần tự chương trình mất gần một giây. Bây giờ, hãy sử dụng thời gian nhàn rỗi của CPU, sử dụng kỹ thuật đa luồng và giảm tổng thời gian thực hiện. Kỹ thuật đa luồng làm giảm thời gian chạy bằng cách phân bổ thời gian CPU cho một tác vụ trong khi các tác vụ khác đang chờ phản hồi I/O. Hãy xem nó hoạt động như thế nào

import threading

start = time.time()

square_thread = threading.Thread(target=calc_square, args=(numbers,))
cube_thread = threading.Thread(target=calc_cube, args=(numbers,))

square_thread.start()
cube_thread.start()

square_thread.join()
cube_thread.join()

end = time.time()

print('Execution Time: {}'.format(end-start))
    2 ^ 2 = 4

    2 ^ 3 = 8

    3 ^ 3 = 27

    3 ^ 2 = 9

    5 ^ 3 = 125
    5 ^ 2 = 25

    8 ^ 2 = 64
    8 ^ 3 = 512

    Execution Time: 0.4172379970550537

Tuyệt vời, thời gian thực hiện chưa đến nửa giây và đó là một cải tiến đáng kể nhờ đa luồng. Hãy khám phá từng dòng mã

Trước tiên, chúng ta cần nhập mô-đun `threading`, mô-đun phân luồng cấp cao với nhiều tính năng hữu ích khác nhau

Chúng tôi sử dụng phương thức xây dựng `Thread` để tạo một cá thể luồng. Trong ví dụ này, phương thức `Thread` nhận hai đầu vào, tên hàm (`mục tiêu`) và các đối số của nó (`args`), dưới dạng một bộ. Chức năng này là những gì sẽ được thực thi khi một luồng bắt đầu thực hiện. Khi chúng ta khởi tạo lớp `Thread`, phương thức xây dựng `Thread` sẽ được gọi tự động và nó sẽ tạo một luồng mới. Nhưng luồng mới sẽ không bắt đầu thực thi ngay lập tức, đây là một tính năng đồng bộ hóa có giá trị cho phép chúng tôi bắt đầu các luồng sau khi tất cả chúng đã được phân bổ

Để bắt đầu thực thi luồng, chúng ta cần gọi riêng phương thức `start` của từng đối tượng luồng. Vì vậy, hai dòng này thực hiện đồng thời các chủ đề hình vuông và khối lập phương

import time
def calc_square(numbers):
    for n in numbers:
        print(f'\n{n} ^ 2 = {n*n}')
        time.sleep(0.1)

def calc_cube(numbers):
    for n in numbers:
        print(f'\n{n} ^ 3 = {n*n*n}')
        time.sleep(0.1)
0

Điều cuối cùng chúng ta cần làm là gọi phương thức `join`, phương thức này yêu cầu một luồng đợi cho đến khi quá trình thực thi của luồng kia hoàn tất

import time
def calc_square(numbers):
    for n in numbers:
        print(f'\n{n} ^ 2 = {n*n}')
        time.sleep(0.1)

def calc_cube(numbers):
    for n in numbers:
        print(f'\n{n} ^ 3 = {n*n*n}')
        time.sleep(0.1)
1

Trong khi phương thức

numbers = [2, 3, 5, 8]
start = time.time()
calc_square(numbers)
calc_cube(numbers)
end = time.time()

print('Execution Time: {}'.format(end-start))
7 tạm dừng việc thực thi hàm
numbers = [2, 3, 5, 8]
start = time.time()
calc_square(numbers)
calc_cube(numbers)
end = time.time()

print('Execution Time: {}'.format(end-start))
2 trong 0. 1 giây, hàm
numbers = [2, 3, 5, 8]
start = time.time()
calc_square(numbers)
calc_cube(numbers)
end = time.time()

print('Execution Time: {}'.format(end-start))
3 được thực thi và in ra khối lập phương của một giá trị trong danh sách, sau đó nó chuyển sang chế độ ngủ và hàm
numbers = [2, 3, 5, 8]
start = time.time()
calc_square(numbers)
calc_cube(numbers)
end = time.time()

print('Execution Time: {}'.format(end-start))
2 sẽ được thực thi. Nói cách khác, hệ điều hành chuyển đổi qua lại giữa các luồng, chạy từng luồng một, dẫn đến cải thiện thời gian chạy của toàn bộ tiến trình.

Phương pháp import time def calc_square(numbers): for n in numbers: print(f'\n{n} ^ 2 = {n*n}') time.sleep(0.1) def calc_cube(numbers): for n in numbers: print(f'\n{n} ^ 3 = {n*n*n}') time.sleep(0.1)7

Mô-đun Python

import time
def calc_square(numbers):
    for n in numbers:
        print(f'\n{n} ^ 2 = {n*n}')
        time.sleep(0.1)

def calc_cube(numbers):
    for n in numbers:
        print(f'\n{n} ^ 3 = {n*n*n}')
        time.sleep(0.1)
7 cung cấp một số chức năng hữu ích giúp bạn quản lý chương trình đa luồng của chúng tôi một cách hiệu quả

**Tên phương thức****Mô tả**Kết quả của phương thứcphân luồng. active_count()trả về số Chủ đề hiện đang hoạt động8phân luồng. current_thread()trả về Chủ đề hiện tại, tương ứng với chủ đề điều khiển của người gọi<_MainThread(MainThread, started 4303996288)>threading.enumerate()trả về danh sách tất cả các Chủ đề hiện đang hoạt động, bao gồm cả chủ đề chính; . main_thread()[<_MainThread(MainThread, started 4303996288)>,,,,, , ,]threading.main_thread()trả về luồng chính<_MainThread(MainThread, started 4303996288)>

Sự kết luận

Đa luồng là một khái niệm rộng trong lập trình nâng cao để triển khai các ứng dụng hiệu suất cao và hướng dẫn này đã đề cập đến những điều cơ bản về đa luồng trong Python. Chúng tôi đã thảo luận về thuật ngữ cơ bản của điện toán đồng thời và song song, sau đó triển khai một chương trình đa luồng cơ bản để tính bình phương và lập phương của một danh sách các số. Chúng ta sẽ thảo luận về một số kỹ thuật đa luồng nâng cao hơn trong tương lai

Khoa học dữ liệu Python Hướng dẫn

Hạn chế chính của luồng với Python là gì?

Thông tin về các Tác giả

Mehdi Lotfinejad

Mehdi là Kỹ sư dữ liệu cao cấp và Trưởng nhóm tại ADA. Anh ấy là một huấn luyện viên chuyên nghiệp, người thích viết các hướng dẫn phân tích dữ liệu

Nhược điểm của đa luồng trong Python là gì?

Các ứng dụng đa luồng và đa ngữ cảnh có những nhược điểm sau. .
Khó khăn khi viết mã. Các ứng dụng đa luồng và đa ngữ cảnh không dễ viết. .
Khó gỡ lỗi. .
Khó khăn trong việc quản lý đồng thời. .
Khó kiểm tra. .
Khó chuyển mã hiện có

Tại sao Python không phải là luồng

Python không an toàn theo luồng và ban đầu được thiết kế với một thứ gọi là GIL hoặc Khóa phiên dịch toàn cầu, đảm bảo các quy trình được thực thi tuần tự trên CPU của máy tính. On the surface, this means Python programs cannot support multiprocessing.

Tại sao luồng Python chậm?

Điều này là do GIL của Python là nút cổ chai ngăn các luồng chạy hoàn toàn đồng thời . Có thể đạt được mức sử dụng CPU tốt nhất có thể bằng cách sử dụng các mô-đun ProcessPoolExecutor hoặc Process giúp phá vỡ GIL và làm cho mã chạy đồng thời hơn.

Vấn đề với chủ đề là gì?

Chúng tôi đã quen thuộc với các loại sự cố phát sinh khi sử dụng luồng. loại trừ lẫn nhau, bế tắc, đảo ngược mức độ ưu tiên và chặn không phù hợp . Chúng tôi đã phát triển các công cụ để khắc phục những sự cố này, nhưng chúng tôi không thể loại bỏ những sự cố này khỏi phần mềm của mình.