Với tôi thì có. Cái gì mà khá vui và cũng hơi khó hiểu ở cái tên tương lai trong tên của mô-đun này. And here as as module duy nhất trong package Concurrent. Trong một số khung nổi bật về học máy hầu hết đều có sử dụng đến mô-đun này
Ok, không dài thêm dòng nữa, chúng ta sẽ đi thẳng vào cách mà mô-đun này được sử dụng và tại sao nó lại có liên quan đến Lập trình đồng thời
Nhiệm vụ I/O-Bound
Đây là những công việc thông thường liên quan đến Mạng, ví dụ như
- Thực hiện một lời gọi API bên ngoài
- Tải tập tin trên Internet
Ngoài ra, các tác vụ I/O bị ràng buộc cũng có thể là các tác vụ thường xuất hiện hàng ngày trên một chiếc máy tính bình thường như đọc, ghi, lưu tệp vào đĩa cứng của bạn hoặc vài thứ về thiết bị xuất chuẩn, thiết bị xuất chuẩn. Một ví dụ đó là câu lệnh
from time import sleep
def just_sleep[n: int = 3] -> None:
""" Do nothing on n seconds [I/O bound]
"""
print[f"Be waiting {n} seconds ..."]
# I/O bound task
sleep[n]
print[f"Done.\n"]
if __name__ == '__main__':
for _ in range[5]:
# "pause" the program 3s by default
just_sleep[]
1 trong PythonVí dụ nhỏ
Cùng xem qua đoạn mã dưới đây nào
def just_sleep[n: int = 3] -> None:
""" Do nothing on n seconds [I/O bound]
"""
print[f"Be waiting {n} seconds ..."]
# I/O bound task
sleep[n]
print[f"Done.\n"]
Ở dây, tôi định nghĩa một chức năng có nhiệm vụ chính là cho phép kéo dài chương trình một khoảng thời gian n giây trước khi ra thông báo có nội dung là “Hoàn tất. ” và xuống dòng
Please run this function in a script python menu as below here
from time import sleep
def just_sleep[n: int = 3] -> None:
""" Do nothing on n seconds [I/O bound]
"""
print[f"Be waiting {n} seconds ..."]
# I/O bound task
sleep[n]
print[f"Done.\n"]
if __name__ == '__main__':
for _ in range[5]:
# "pause" the program 3s by default
just_sleep[]
Về cơ bản, chúng ta đang yêu cầu chương trình tạm dừng chương trình 5 lần và mỗi lần dừng khoảng 3 giây. Một cách tính tuyến tính thì chúng ta sẽ phải mất khoảng đâu đó tầm 5 x 3 = 15 [giây] để đoạn script này chạy xong. Để chắc chắn rằng tôi không thắc mắc logic lỗi gì ở đây thì chúng ta nên thực hiện một công đoạn nhỏ nữa là thêm
from time import sleep
def just_sleep[n: int = 3] -> None:
""" Do nothing on n seconds [I/O bound]
"""
print[f"Be waiting {n} seconds ..."]
# I/O bound task
sleep[n]
print[f"Done.\n"]
if __name__ == '__main__':
for _ in range[5]:
# "pause" the program 3s by default
just_sleep[]
2 vào chương trình này# thread_pool1.py
import time
from time import sleep
def just_sleep[n: int = 3] -> None:
""" Do nothing on n seconds [I/O bound]
"""
print[f"Be waiting {n} seconds ..."]
# I/O bound task
sleep[n]
print[f"Done.\n"]
if __name__ == '__main__':
start_time = time.time[]
for _ in range[5]:
# "pause" the program 3s by default
just_sleep[]
print["Elapsed time: {:.2f} seconds".format[time.time[] - start_time]]
Cùng chạy thử coi nào
đồng thời. tương lai đến để chơi
Bạn có thử đặt câu hỏi rằng làm sao chúng ta có thể làm được gì trong quá trình chờ đợi 3 giây kia được không?
Hãy lấy từ cuộc sống thực nhé, bạn sẽ làm gì khi đang đứng chờ xe buýt, hay chờ đến lượt mình tính tiền trong siêu thị. Có thể là bất cứ điều gì đúng chứ nhỉ?, lướt fb chẳng hạn, nhưng tôi tin là nó sẽ khiến bạn đỡ thấy chán hơn trong lúc đó. Ở đây cũng vậy, trong lúc
from time import sleep
def just_sleep[n: int = 3] -> None:
""" Do nothing on n seconds [I/O bound]
"""
print[f"Be waiting {n} seconds ..."]
# I/O bound task
sleep[n]
print[f"Done.\n"]
if __name__ == '__main__':
for _ in range[5]:
# "pause" the program 3s by default
just_sleep[]
3 thì chúng ta hoàn toàn có thể thực hiện một from time import sleep
def just_sleep[n: int = 3] -> None:
""" Do nothing on n seconds [I/O bound]
"""
print[f"Be waiting {n} seconds ..."]
# I/O bound task
sleep[n]
print[f"Done.\n"]
if __name__ == '__main__':
for _ in range[5]:
# "pause" the program 3s by default
just_sleep[]
3 khác. Tại sao không khi Python hoàn toàn cho phép bạn thực hiện điều đó. 🔥🔥🔥Ngoại lệ
Thực ra, chúng ta không thể thực hiện một lúc nhiều hơn một nhiệm vụ tại một thời điểm trong Python khi mà cái được gọi là GLOBAL INTERPRETER LOCK [GIL] sẽ ngăn chương trình thực thi “bài hát” nhiều nhiệm vụ một lúc
Về mặt lịch sử, thì Python được thiết kế sao cho nó chắc chắn được rằng thông dịch viên chỉ thực hiện một nhiệm vụ tại một thời điểm trên cùng một chủ đề. Tuy nhiên cũng đã có những nỗ lực để loại bỏ GIL ở Python 1. 5. Tuy nhiên, theo Guido van van Rossum [người tạo ngôn ngữ lập trình Python] đã có những cảnh báo định hướng tốt nhất về việc loại bỏ hoàn toàn GIL, khi nó có những tác động về mặt hiệu năng nhất định
Hãy sử dụng Class để thực hiện điều mà ta mong muốn, đó là chạy nhiều chức năng
from time import sleep
def just_sleep[n: int = 3] -> None:
""" Do nothing on n seconds [I/O bound]
"""
print[f"Be waiting {n} seconds ..."]
# I/O bound task
sleep[n]
print[f"Done.\n"]
if __name__ == '__main__':
for _ in range[5]:
# "pause" the program 3s by default
just_sleep[]
0 một cách không đồng bộdef future_fn[fn]:
""" Using ThreadPoolExecutor class to
create a convention thread manager
"""
with ThreadPoolExecutor[max_workers=5] as executor:
"""
.map[] function in concurrent futures is just the same
as .map built-in function of Python language
arg1: fn [callable] to execute
arg2: an iterable to pass in as argument for the above fn
"""
executor.map[fn, [3]*5]
Ở đây, chúng ta sử dụng hàm của lớp ThreadPoolExecutor, là một lớp con của lớp trừu tượng
# thread_pool2.py
import time
from time import sleep
from concurrent.futures import ThreadPoolExecutor
def just_sleep[n: int = 3] -> None:
""" Do nothing on n seconds [I/O bound]
"""
print[f"Be waiting {n} seconds ..."]
# I/O bound task
sleep[n]
print[f"Done.\n"]
def future_fn[fn]:
""" Using ThreadPoolExecutor class to
create a convention thread manager
"""
with ThreadPoolExecutor[max_workers=5] as executor:
"""
.map[] function in concurrent futures is just the same
as .map built-in function of Python language
arg1: fn [callable] to execute
arg2: an iterable to pass in as argument for the above fn
"""
executor.map[fn, [3]*5]
if __name__ == '__main__':
start_time = time.time[]
# current_fn[just_sleep]
future_fn[just_sleep]
print[f"Elapsed time: {time.time[] - start_time} seconds"]
Please run back and view any results
3 giây. That is running time when using ThreadPoolExecutor
Một số giải thích
Chúng ta sẽ thảo luận một chút về công việc tại sao, khi sử dụng module futures lại cho kết quả như vậy
Như tôi đã đề cập ở trên về vấn đề GIL. Bạn sẽ không thể thực hiện nhiều hơn là một nhiệm vụ tại một thời điểm và cách để vượt qua vấn đề này là sử dụng đến cách tiếp cận khác và vượt ra khỏi phạm vi của bài viết này. [bật mí. sử dụng đa xử lý mô-đun]
Đúng vậy, với nhiệm vụ ràng buộc I/O thì chúng ta hoàn toàn có một cách để vượt qua giới hạn chế độ này. Hãy cùng nhau xem qua slide bên dưới, được lấy từ bài nói chuyện của David Beazley [slide]
Như bạn có thể nhìn thấy, khi một luồng thực hiện công việc của nó và nếu tồn tại các hoạt động I/O [không giới hạn như tệp đọc-ghi], thì tại thời điểm đó GIL đã được giải nén và nếu chương trình của chúng tôi
Luồng, đa xử lý và đồng thời. tương lai
Thật ra, module concurrent. futures chỉ là một mô-đun cấp cao. Nó hoàn toàn dựa trên hai mô-đun khác của Python, đó là phân luồng và đa xử lý. Nó cho phép những người mới bắt đầu làm quen với lập trình đồng thời có một công cụ để có thể chơi với công việc xử lý tác vụ đa luồng hoặc tận dụng tính toán đa lõi của những CPU đời mới một cách dễ dàng và nhanh chóng. Tuy nhiên, đổi lại thì chúng tôi sẽ không thể tác động sâu vào bên trong lõi của hai mô-đun kia trong những vấn đề phức tạp hơn
Một số ví dụ khác
import concurrent.futures
import urllib.request
URLS = ['//dantri.com.vn/',
'//vnexpress.net/',
'//kenh14.vn/',
'//zingnews.vn/',
'//genk.vn/']
# Retrieve a single page and report the URL and contents
def load_url[url, timeout]:
with urllib.request.urlopen[url, timeout=timeout] as conn:
return conn.read[]
# We can use a with statement to ensure threads are cleaned up promptly
with concurrent.futures.ThreadPoolExecutor[max_workers=5] as executor:
# Start the load operations and mark each future with its URL
future_to_url = dict[]
for url in URLS:
future = executor.submit[load_url, url, 60]
future_to_url.update[{future: url}] # [*]
for future in concurrent.futures.as_completed[future_to_url]:
url = future_to_url[future]
try:
data = future.result[]
except Exception as exc:
print['%r generated an exception: %s' % [url, exc]]
else:
print['%r page is %d bytes' % [url, len[data]]]
Ví dụ trên được lấy từ , được tôi chỉnh sửa lại một chút để làm rõ ràng hơn cho người đọc. Nhiệm vụ chính của chức năng
from time import sleep
def just_sleep[n: int = 3] -> None:
""" Do nothing on n seconds [I/O bound]
"""
print[f"Be waiting {n} seconds ..."]
# I/O bound task
sleep[n]
print[f"Done.\n"]
if __name__ == '__main__':
for _ in range[5]:
# "pause" the program 3s by default
just_sleep[]
1 là thực hiện các yêu cầu GET đến một số trang báo mạng nổi bật ở Việt Nam và sau khi nhận được phản hồi thì tốc độ phản hồi của byte theo từng trang. Đương nhiên là ví dụ này được chạy hoàn toàn bất đồng bộỞ ví dụ này, chúng ta sử dụng hai chức năng khác đó là và. Hàm đầu tiên thuộc về phân lớp ThreadPoolExecutor, còn cái sau là hàm cấp mô-đun. Như ví dụ trước đó của chúng ta, chức năng. map[] used to run concurrent. Có một sự khác biệt ở chức năng này đó là nó chỉ cho phép chúng ta sử dụng duy nhất một chức năng kiểm soát các dữ liệu khác nhau, hoàn toàn giống như cách mà. map[] phương thức tích hợp sẵn của Python được sử dụng. Trong khi đó, ở ví dụ hiện tại, với việc sử dụng. submit[], chúng ta hoàn toàn có thể chạy thông qua một vòng lặp cho và thực hiện chuyển vào các chức năng khác nhau mà chúng ta muốn thực hiện. Như vậy nó sẽ linh hoạt hơn. Như vậy thì ở ví dụ này, chúng ta chỉ sử dụng duy nhất một chức năng cho việc chạy bất đồng bộ đó là
from time import sleep
def just_sleep[n: int = 3] -> None:
""" Do nothing on n seconds [I/O bound]
"""
print[f"Be waiting {n} seconds ..."]
# I/O bound task
sleep[n]
print[f"Done.\n"]
if __name__ == '__main__':
for _ in range[5]:
# "pause" the program 3s by default
just_sleep[]
1. Ngoài ra, giá trị trả về của chức năng. submit[] sẽ là một đối tượng, về cơ bản là một đối tượng mà cho phép chúng ta giữ nguyên trạng thái cũng như kết quả sau khi nhiệm vụ được thực hiện hoàn thành trong cơ chế bất đồng bộ của mô-đun tương laiỞ dòng [*] trong đoạn mã trên, ta tạo ra một từ điển để ánh xạ từng URL đến các đối tượng tương lai tương ứng của chúng để thuận tiện cho việc kiểm tra được kết quả trả về cũng như quá trình gỡ lỗi
from time import sleep
def just_sleep[n: int = 3] -> None:
""" Do nothing on n seconds [I/O bound]
"""
print[f"Be waiting {n} seconds ..."]
# I/O bound task
sleep[n]
print[f"Done.\n"]
if __name__ == '__main__':
for _ in range[5]:
# "pause" the program 3s by default
just_sleep[]
3 may be function quan trọng nhất ở đoạn này. tại sao? . Và đó là lý do mà from time import sleep
def just_sleep[n: int = 3] -> None:
""" Do nothing on n seconds [I/O bound]
"""
print[f"Be waiting {n} seconds ..."]
# I/O bound task
sleep[n]
print[f"Done.\n"]
if __name__ == '__main__':
for _ in range[5]:
# "pause" the program 3s by default
just_sleep[]
3 có mặt trong mô-đun nàyKết quả
Tôi sẽ tạm dừng bài blog ở đây. Chủ để về Lập trình đồng thời không phải sớm một chiều chúng ta có thể hiểu toàn diện được, tuy nhiên, bài viết này giống như một cái bản tóm tắt cơ bản về cách chúng ta có thể thực hiện CP mà không cần quá nhiều nỗ lực . tương lai Python và được đọc từ cuốn sách rất hay của Luciano Ramalho với tiêu đề là Fluent Python