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ànHã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ụ webnumbers = [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]
1Trong 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
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]
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ả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
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