Chúng ta đều quen thuộc với khái niệm hàm, cái mà còn được gọi là subroutine (đoạn chương trình con), procedure (thủ tục) và subprocess (tiến trình con), v.v… Hàm là một chuỗi các chỉ thị được đóng gọi dưới dạng một đơn vị để thực hiện một tác vụ cụ thể. Khi logic của một hàm phức tạp được chia ra thành các hàm nhỏ hơn, thì các hàm nhỏ này được gọi là các helper function – hàm phụ trợ hoặc subroutines – các đoạn chương trình con.
Subroutines – các đoạn chương trình con trong Puthon đều được gọi đến bởi main function – hàm main/hàm chính, cái mà chịu trách nhiệm điều phối việc sử dụng các đoạn chương trình con này. Các subroutines chỉ có một entry point (điểm để đi vào) duy nhất.
Coroutine là sự khái quát của subroutine (đoạn chương trình con). Chúng thường được sử dụng cho xử lý đa nhiệm có tính kết hợp, trong đó một tiến trình (process) sẽ tự nguyện cho đi quyền điều khiển thực thi một cách định kỳ, hoặc khi tiến trình đó ở trạng thái nghỉ (idle), để cho phép nhiều ứng dụng có thể được chạy cùng lúc. Sự khác biệt giữa Coroutine và subroutine là:
– Không giống như các subroutines, các coroutines sở hữu rất nhiều entry points (điểm đi vào) phục vụ cho việc tạm dừng thực thi và tiếp tục thực thi. Coroutine có thể tạm dừng việc thực thi của nó và chuyển quyền điều khiển chương trình sang cho coroutine khác và có thể tiếp tục thực thi trở lại từ cái thời điểm mà nó đã cho đi quyền điều khiển.
Nội dung chính
1. Coroutine và Thread
Tới đây, bạn có thể nghĩ rằng Coroutine và Thread khác nhau như thế nào, bởi vì dường như chúng đều thực hiện cùng một công việc.
Trong trường hợp các threads, Việc chuyển đổi giữa các threads theo một lịch trình cụ thể được thực hiện bởi hệ điều hành (hoặc run time environment – môi trường thời gian chạy). Trong khi đối với các coroutine, các lập trình viên hoặc ngôn ngữ lập trình sẽ quyết định khi nào chuyển đổi giữa các coroutines. Các coroutines xử lý đa tác vụ một cách kết hợp bằng cách tạm dừng và trở lại thực thi tiếp tại một điểm/mốc được chỉ định bởi lập trình viên.
2. Coroutine trong Python
Trong Python, các coroutines là tương tự với các generators nhưng có thêm một vài phương thức bổ sung và thay đổi nhỏ trong cách sử dụng câu lệnh yield. Các generators tạo ra dữ liệu dành cho các iteration (các phép lặp), còn các coroutines thì sử dụng/tiêu thụ dữ liệu.
Trong Python 2.5, một sửa đổi nhỏ cho câu lệnh yield đã được giới thiệu, đó là yield cũng có thể được sử dụng như một expression – biểu thức.
Ví dụ, ở phía bên phải của phép gán sau:
line = (yield)
bất kỳ giá trị nào chúng ta gửi đến coroutine đều sẽ được bắt lại và được trả về bởi biểu thức (yield).
Có thể gửi một giá trị đến coroutine bằng phương thức send().
Ví dụ, giả sử có một coroutine thực hiện in ra tên mà có tiền tố “Dear”. Chúng ta sẽ gửi các tên tới coroutine bằng cách sử dụng phương thức send().
# -----------------------------------------------------------
#Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
#@author cafedevn
#Contact: cafedevn@gmail.com
#Fanpage: https://www.facebook.com/cafedevn
#Instagram: https://instagram.com/cafedevn
#Twitter: https://twitter.com/CafedeVn
#Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
# -----------------------------------------------------------
# Python3 program for demonstrating
# coroutine execution
def print_name(prefix):
print("Searching prefix:{}".format(prefix))
while True:
name = (yield)
if prefix in name:
print(name)
# calling coroutine, nothing will happen
corou = print_name("Dear")
# This will start execution of coroutine and
# Prints first line "Searchig prefix..."
# and advance execution to the first yield expression
corou.__next__()
# sending inputs
corou.send("Atul")
corou.send("Dear Atul")
Kết quả in ra là:
Searching prefix:Dear
Dear Atul
3. Cơ chế thực thi của coroutine
Coroutine có cơ chế thực thi tương tự như generator. Khi chúng ta gọi đến coroutine, sẽ không có điều gì xảy ra, nó chỉ chạy để phản hồi lại cho phương thức next() và send(). Điều này có thể được nhìn thấy rõ ràng trong ví dụ trên, chỉ sau khi gọi đến phương thức __next__(), thì couroutine có tên là corou mới bắt đầu thực thi. Sau lời gọi hàm __next__() này, thực thi sẽ được chuyển tới câu lệnh yield đầu tiên, lúc này thực thi sẽ được tạm dừng và chờ giá trị được gửi tới cho đối tượng corou. Khi giá rtij đầu tiên được gửi tới cho nó, đối tượng corou sẽ kiểm tra phần tiền tố và in ra tên nếu tiền tố mong muốn có xuất hiện. Sau khi in ra tên, nó sẽ đi qua vòng lặp cho đến khi gặp lại biểu thức name = (yield).
4. Cách đóng lại một Coroutine
Coroutine có thể chạy mãi mãi, để đóng lại một coroutine, ta phải sử dụng phương thức close(). Khi coroutine được đóng lại, nó sẽ ném ra ngoại lệ GeneratorExit có thể được bắt lại theo cách thông thường. Sau khi đã đóng cái coroutine lại, nếu chúng ta thử gửi đi các giá trị, nó sẽ đưa ra lỗi/ngoại lệ StopIteration. Dưới đây là một ví dụ đơn giản mô tả cách đóng lại một coroutine:
# -----------------------------------------------------------
#Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
#@author cafedevn
#Contact: cafedevn@gmail.com
#Fanpage: https://www.facebook.com/cafedevn
#Instagram: https://instagram.com/cafedevn
#Twitter: https://twitter.com/CafedeVn
#Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
# -----------------------------------------------------------
# Python3 program for demonstrating
# closing a coroutine
def print_name(prefix):
print("Searching prefix:{}".format(prefix))
try :
while True:
name = (yield)
if prefix in name:
print(name)
except GeneratorExit:
print("Closing coroutine!!")
corou = print_name("Dear")
corou.__next__()
corou.send("Cafedev")
corou.send("Dear Cafedev")
corou.close()
Kết quả in ra là:
Searching prefix:Dear
Dear Cafedev
Closing coroutine!!
5. Ghép nối các coroutine để tạo ra đường ống
Các coroutine có thể được sử dụng để thiết lập các ống dẫn – pipe. Chúng ta có thể ghép nối các coroutine lại với nhau (mỗi coroutine được coi là một ống dẫn) và đẩy dữ liệu đi qua đường ống dẫn bằng cách sử dụng phương thức send(). Một ống dẫn cần phải có được:
– Một cái initial source – nguồn khởi tạo (hay còn gọi là producer), cái mà sẽ dẫn xuất ra toàn bộ đường ống. Producer thường không phải là một coroutine, nó chỉ là một phương thức đơn giản.
– Một cái sink, là điểm cuối của đường ống dẫn. Một sink có thể thu thập tất cả dữ liệu và hiển thị chúng.
Dưới đây là một ví dụ đơn giản về chainning coroutines – ghép nối các coroutines
Đoạn code mô tả cơ chế coroutine chaining:
# Python3 program for demonstrating
# coroutine chaining
def producer(sentence, next_coroutine):
'''
Producer which just split strings and
feed it to pattern_filter coroutine
'''
tokens = sentence.split(" ")
for token in tokens:
next_coroutine.send(token)
next_coroutine.close()
def pattern_filter(pattern="ing", next_coroutine=None):
'''
Search for pattern in received token
and if pattern got matched, send it to
print_token() coroutine for printing
'''
print("Searching for {}".format(pattern))
try:
while True:
token = (yield)
if pattern in token:
next_coroutine.send(token)
except GeneratorExit:
print("Done with filtering!!")
def print_token():
'''
Act as a sink, simply print the
received tokens
'''
print("I'm sink, i'll print tokens")
try:
while True:
token = (yield)
print(token)
except GeneratorExit:
print("Done with printing!")
pt = print_token()
pt.__next__()
pf = pattern_filter(next_coroutine = pt)
pf.__next__()
sentence = "Bob is running behind a fast moving car"
producer(sentence, pf)
Kết quả in ra là:
I'm sink, i'll print tokens
Searching for ing
running
moving
Done with filtering!!
Done with printing!
6. Tài liệu tham khảo thêm
Nguồn và Tài liệu tiếng anh tham khảo:
Tài liệu từ cafedev:
- Full series tự học Python từ cơ bản tới nâng cao tại đây nha.
- Ebook về python tại đây.
- Các series tự học lập trình khác
Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của cafedev để nhận được nhiều hơn nữa:
Chào thân ái và quyết thắng!