Không đồng bộ, trong lập trình máy tính, đề cập đến sự xuất hiện của các sự kiện độc lập với luồng chương trình chính và cách xử lý các sự kiện đó. Đây có thể là các sự kiện “bên ngoài” như tín hiệu xuất hiện, hoặc hành động do chương trình khởi xướng diễn ra đồng thời với việc thực hiện chương trình mà không bị chặn chương trình để chờ kết quả. Đầu vào/đầu ra không đồng bộ là một ví dụ về nguyên nhân thứ hai của sự không đồng bộ và cho phép các chương trình ra lệnh cho các thiết bị lưu trữ hoặc mạng phục vụ các yêu cầu này trong khi bộ xử lý tiếp tục thực thi chương trình. Làm như vậy cung cấp một mức độ song song
Hình ảnh dưới đây rất hấp dẫn và chúng ta sẽ thấy một vài giải pháp hấp dẫn cho các vấn đề tương tự
Hãy nhớ rằng, các luồng trong Python là các luồng gốc, nhưng do một số chính sách [có trong cpython], GIL [Khóa phiên dịch toàn cầu] sẽ không cho phép 2 luồng chạy cùng một lúc. Do đó, các luồng Python không hoạt động tốt với nhau và họ không thích sử dụng chúng
- Nó được duy trì
- Fork một chủ đề mới, phải mất một thời gian dài
Chúng ta có thể thấy rằng tất cả các luồng và quy trình đều có không gian bộ nhớ riêng, vì vậy chúng ta có thể thực hiện mọi việc độc lập với luồng chính hoặc quy trình chính.
Mặt khác, vòng lặp sự kiện duy trì các tác vụ, các tác vụ này chia sẻ bộ nhớ chung và chúng ta sẽ phải trả lời câu hỏi.
0 1 1 2 3 5 8 13 21 34
0Khi nào chúng ta sẽ sử dụng vòng lặp sự kiện, luồng hay quy trình?
Trong khoa học máy tính, chúng ta có thể chia nhiệm vụ thành 2 loại
- Xếp hạng gắn liền với CPU
Trong khoa học máy tính, máy tính bị giới hạn bởi CPU [hoặc giới hạn máy tính] khi thời gian cần thiết để hoàn thành một tác vụ chủ yếu được xác định bởi tốc độ của bộ xử lý trung tâm. Mức sử dụng của bộ xử lý cao, có thể đạt mức sử dụng 100% trong vài giây hoặc vài phút. Các gaêt do baii đội nhóm đến có thể được xử lý chậm hoặc trì hoãn vô thời hạn
- Xếp hạng gắn liền với I/
Hàng đợi I/O đề cập đến tình trạng trong đó thời gian cần thiết để hoàn thành một thao tác phím được xác định bởi lượng thời gian hoàn thành các thao tác nhập/xuất. Điều này trái ngược với một tác vụ liên quan đến CPU. Tình trạng này xảy ra khi tốc độ truy xuất dữ liệu chậm hơn tốc độ sử dụng, hay nói cách khác, việc truy xuất dữ liệu mất nhiều thời gian hơn là xử lý dữ liệu đó.
Ví dụ
sử dụng đa xử lý [luồng gốc] cho liên kết I/O
Nó sử dụng các luồng gốc [hoặc luồng gốc] để thực hiện các tác vụ gắn liền với I/O
Jason đã hỏi David rằng 'Bạn sử dụng cái gì từ điển hộm nay?'
Không, nó không phải là một giải pháp hoàn hảo. Thay vào đó, David nên thực hiện các nhiệm vụ khác cho đến khi Ms. Tee nói rằng 'Này David, nhiệm vụ của bạn vẫn chưa được hoàn thành, hãy sửa chúng đi' 😥
Chính xác thì đây là cách hoạt động của vòng lặp sự kiện và các tác vụ bị ràng buộc bởi I/O
Sử dụng vòng lặp sự kiện để chạy các tác vụ gắn liền với CPU
Nhóm của bạn có 5 thành viên và 5 nhiệm vụ, nhưng chỉ có David làm tất cả các nhiệm vụ đó. Bởi vì mỗi nhiệm vụ phải được cam kết vào cuối mỗi ngày, David thực hiện một nhiệm vụ tại một thời điểm và chuyển sang nhiệm vụ khác. Chính vì vậy cuối tuần anh em hãy tải kiệt và bù 😷
Không, chúng tôi chỉ có 5 thành viên, tại sao chúng tôi lại đẩy tất cả công việc của David?
Đây là cách nó hoạt động
Qua ví dụ trên, vòng lặp sự kiện ta thờ khách xác định phần số hội lạ trong bạn để liên kết giới hạn số lượng IO. Chúng được sử dụng trong các hệ thống sự kiện và các hệ thống tương tự. Chúng tôi có thể cung cấp giải pháp tốt nhất cho các vấn đề liên quan đến ràng buộc IO
Các vấn đề trong mô hình vòng lặp sự kiện
Chúng tôi đã biết rằng chúng tôi sẽ có bối cảnh là một người hâm mộ duy nhất. Bối cảnh này chứa các biến và chúng được giải phóng khi chức năng kết thúc [giải phóng ngăn xếp].
Trong I/O-bound task, chúng ta sẽ có một vài lệnh để lấy dữ liệu [IO operation] nhưng lúc đó chúng ta sẽ cần đợi task hoàn thành [như ví dụ của David, chúng ta sẽ có thể tạm thời hủy bỏ nhiệm vụ đang chờ kiểm tra chuyển sang nhiệm vụ khác rồi quay lại làm nhiệm vụ của lần trước]
Nhụ động các ngết là yểu bại để, làm sao ngườn ta có bài ra các ngáết trong hàm mà vẫn giữ nguyên ngữ cảnh hàm dại của phần thi tạm thời?
Lúc đó callee đang làm công việc điều khiển chương trình [program control] và caller đang gọi, vòng lặp sự kiện ở đây và chúng ta cần bắt đầu tại điểm. thi tân
Giải pháp ở đây là sử dụng một coroutine
Coroutine là gì?
Donald Knuth nói
Chương trình con tương tự như coroutines
Đúng vậy, tóm lại, các hàm bình thường chúng ta sẽ sử dụng [hàm được giải phóng khỏi ngữ cảnh khi thoát khỏi hàm] trong trường hợp đặc biệt của coroutine - nơi ngữ cảnh có thể được giữ khi nó được sử dụng tạm thời
Tại sao coroutine hữu ích cho hệ thống sự kiện?
- Có lịch trình không ưu tiên
- Bạn có thể tạm dừng và tiếp tục bất cứ lúc nào, nếu dữ liệu đang truyền phát, chúng tôi có thể tiết kiệm bộ nhớ
- có thể duy trì một trạng thái của tâm trí
- Với các ràng buộc I/O, bộ nhớ tối đa của coroutine và CPU
- Chúng tôi nhỏ bé
Người độc thân
ProcessNative threadLuồng xanhGoroutineCoroutineBộ môi≤ 8Mb≤ Nx2Mb≥ 64Kb≥ 8Kb≥ 0MbQuản lý bởi OSYesYesNoNoNoKhông gian khám phá đến điệnCóNoNoNoNoPre-emptive scheduleYesYesNoRangng bài hátYesYesNoYesNocâu hỏi là ở đó. Vì vậy, coroutine làm tấm bản dịchnh nàe?
Làm thế nào để cài đặt một coroutine?
Về cơ bản, chúng ta cố gắng lưu trạng thái của hàm trong biến
0 1 1 2 3 5 8 13 21 34
1 và biến 0 1 1 2 3 5 8 13 21 34
2 là đóng như hình. Trước khi tạm thời dừng chức năng [tạm dừng], biến 0 1 1 2 3 5 8 13 21 34
2 được đặt về điểm bắt đầu khi nó được khôi phục [tiếp tục].Trong đoạn mã này, điểm chính là biến
0 1 1 2 3 5 8 13 21 34
2 và mã có thể tiếp tục và tạm dừng coroutine bằng cách sử dụng 0 1 1 2 3 5 8 13 21 34
5Và ở phía dưới, chúng tôi đang chuyển đổi từ mã Python sang mã C trong đào tạo
def coroutine[]:
i = 0
while 1:
yield i
i += 1
co = coroutine[]
next[co]
next[co]
next[co]
Bạn có thể chuyển mã Python này sang C không?
def fib[]:
a, b = 0, 1
while True:
yield a
a, b = a + b, a
co = fib[]
for _ in range[10]:
print[next[co], end=' ']
Sau đó, bạn sẽ thấy quản lý như thế này
0 1 1 2 3 5 8 13 21 34
Tôi có thể xây dựng bất kỳ coroutine nào trong C. Bạn có thể làm điều đó?
#include
int fib[] {
static int i, __resume__ = 0;
static a = 0, b = 1, c;
switch [__resume__] {
case 0:
for [i = 0;; ++i] {
if [!__resume__] __resume__ = 1;
c = a + b;
b = a;
a = c;
return a;
case 1:;
}
}
}
int main[] {
for [int i = 0; i < 10; ++i] {
printf["%d ", fib[]];
}
return 0;
}
def say[]:
yield "C"
yield "Java"
yield "Python"
co = say[]
print[next[co]]
print[next[co]]
print[next[co]]
print[next[co]]
Kết quả có thể được nhìn thấy
C
Java
Python
---------------------------------------------------------------------------
StopIteration Traceback [most recent call last]
in
8 print[next[co]]
9 print[next[co]]
---> 10 print[next[co]]
StopIteration:
Chúng ta có thể thấy rằng coroutine cần một không gian bộ nhớ tĩnh để lưu trạng thái của nó khi nó treo và phục hồi mà không làm mất bối cảnh của nó. Trong C, không có biến tĩnh, được hệ điều hành duy trì khi một chức năng được giải phóng. Trong Python, ngữ cảnh của hàm được lưu trữ trong khung ngăn xếp
Hãy nghĩ về các coroutine giống như có các phần của một chương trình, không có bộ nhớ của riêng chúng, không có bài hát thực thi và rất ít
Coroutine làm giảm các lỗi xử lý của luồng [flow] và gây ra các vấn đề và chúng tôi nghĩ rằng nó là giải pháp tốt nhất cho các tác vụ liên quan đến mạng vì nó chỉ tồn tại trong 1 luồng.
Trong Python, chúng ta có thể sử dụng chức năng coroutine bằng cách sử dụng lệnh
0 1 1 2 3 5 8 13 21 34
. Khi chúng tôi gọi, chúng tôi trả về một coroutine thay vì kết quả cuối cùngSau đó, kết quả có thể được nhìn thấy
Starting
Consume data
Hello World
Generator là một trường hợp đặc biệt của coroutine, chúng ta chỉ có thể tạo ra dữ liệu chứ không thể tiêu thụ dữ liệu.
def g1[]:
for i in range[10]:
yield i
def g2[]:
for i in range[10, 20]:
yield i
def g[]:
for i in g1[]:
yield i
for i in g2[]:
yield i
list[g[]]
Và đây là kết quả
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
Chúng tôi có thể cấu trúc lại mã với
0 1 1 2 3 5 8 13 21 34
7def fib[]:
a, b = 0, 1
while True:
yield a
a, b = a + b, a
co = fib[]
for _ in range[10]:
print[next[co], end=' ']
0Xây dựng cây nhị phân với 0 1 1 2 3 5 8 13 21 34
7
0 1 1 2 3 5 8 13 21 34
Dựng cây với 0 1 1 2 3 5 8 13 21 34
9
0 1 1 2 3 5 8 13 21 34
def fib[]:
a, b = 0, 1
while True:
yield a
a, b = a + b, a
co = fib[]
for _ in range[10]:
print[next[co], end=' ']
1_______7_______2Ứng dụng của coroutine
1. Máy chủ TCP không khớp
Trong trường hợp này, máy chủ TCP là một hệ thống sự kiện
nguồn sự kiện. ổ cắm nghe và kết nối ổ cắm
number of trú bản, nười ta có hai đội khách sạn. EVENT_READ, EVENT_WRITE
và các task là coroutine, mỗi task sẽ xử lý 1 sự kiện của 1 kết nối tại 1 thời điểm
Chúng tôi cũng có một vòng lặp sự kiện, chúng tôi có bộ mô tả tệp nóng ghép kênh I/O
Bạn có thể chạy nó
def fib[]:
a, b = 0, 1
while True:
yield a
a, b = a + b, a
co = fib[]
for _ in range[10]:
print[next[co], end=' ']
3Lập kế hoạch và nhiệm vụ trong các thư viện thực.
2. hệ thống truyền phát
Chúng ta có thể sử dụng coroutines để xây dựng một hệ thống xử lý dữ liệu. Về cơ bản, hệ thống phân tách các khối logic nhỏ. Chúng được đặt trong coroutines với ngữ cảnh riêng biệt. Bạn có thể xem hình ảnh của chúng tôi dưới đây
Nguồn sự kiện có thể là Redis pub/sub, Kafka, RabbitMQ hoặc tương tác của người dùng,
Chúng ta có thể mô tả bất kỳ loại hệ thống nào nếu chúng ta có thể tạo một khối logic cụ thể. bộ lọc dữ liệu, bộ điều kiện, bộ chọn, bộ quảng bá
Ví dụ. chỉ xây dựng địa chỉ IP của máy chủ web
Trước tiên, bạn cần một tệp nhật ký
Sau đó chạy
Kết quả có thể được nhìn thấy
def fib[]:
a, b = 0, 1
while True:
yield a
a, b = a + b, a
co = fib[]
for _ in range[10]:
print[next[co], end=' ']
4Chúc may mắn
Chúng ta sẽ có thể bọc các luồng trong một coroutine, phải không?
Nói một cách đơn giản, chúng tôi sẽ sử dụng các sợi chỉ để làm ấm máy
OK, chúng ta hãy lập một kế hoạch
Trong sơ đồ tàu, chúng tôi chuyển logic vào các luồng và sử dụng hàng đợi làm kênh để giao tiếp với các luồng
Không chỉ vậy, bộ đệm còn bị đóng nếu tốc độ đầu vào lớn hơn tốc độ của đơn vị xử lý.
def fib[]:
a, b = 0, 1
while True:
yield a
a, b = a + b, a
co = fib[]
for _ in range[10]:
print[next[co], end=' ']
5Và chạy như thế này
sau đó
def fib[]:
a, b = 0, 1
while True:
yield a
a, b = a + b, a
co = fib[]
for _ in range[10]:
print[next[co], end=' ']
6Oh, tại sao kết quả là trống rỗng?
Tôi muốn bạn gợi ý cho tôi, có thêm lệnh
#include
int fib[] {
static int i, __resume__ = 0;
static a = 0, b = 1, c;
switch [__resume__] {
case 0:
for [i = 0;; ++i] {
if [!__resume__] __resume__ = 1;
c = a + b;
b = a;
a = c;
return a;
case 1:;
}
}
}
int main[] {
for [int i = 0; i < 10; ++i] {
printf["%d ", fib[]];
}
return 0;
}
0 trước lệnh #include
int fib[] {
static int i, __resume__ = 0;
static a = 0, b = 1, c;
switch [__resume__] {
case 0:
for [i = 0;; ++i] {
if [!__resume__] __resume__ = 1;
c = a + b;
b = a;
a = c;
return a;
case 1:;
}
}
}
int main[] {
for [int i = 0; i < 10; ++i] {
printf["%d ", fib[]];
}
return 0;
}
1 xem cái này. nụ cườiLưu nó
Khi chúng ta sử dụng coroutine, chúng ta nên xem coroutine có thể bị quá tải hay không. Tức là, tại một thời điểm, coroutine đó chỉ có thể được đẩy vào dữ liệu và nó chỉ đang xử lý dữ liệu bên trong hộp, phải không?
Vẽ thiết kế DAG
Chỉ gọi
2 trong cùng một chuỗi, nhưng chỉ gọi#include int fib[] { static int i, __resume__ = 0; static a = 0, b = 1, c; switch [__resume__] { case 0: for [i = 0;; ++i] { if [!__resume__] __resume__ = 1; c = a + b; b = a; a = c; return a; case 1:; } } } int main[] { for [int i = 0; i < 10; ++i] { printf["%d ", fib[]]; } return 0; }
2 trong một chuỗi#include int fib[] { static int i, __resume__ = 0; static a = 0, b = 1, c; switch [__resume__] { case 0: for [i = 0;; ++i] { if [!__resume__] __resume__ = 1; c = a + b; b = a; a = c; return a; case 1:; } } } int main[] { for [int i = 0; i < 10; ++i] { printf["%d ", fib[]]; } return 0; }
3. Bộ lập lịch hệ điều hành nóng
Khi một lệnh được nhập vào một tác vụ
#include
int fib[] {
static int i, __resume__ = 0;
static a = 0, b = 1, c;
switch [__resume__] {
case 0:
for [i = 0;; ++i] {
if [!__resume__] __resume__ = 1;
c = a + b;
b = a;
a = c;
return a;
case 1:;
}
}
}
int main[] {
for [int i = 0; i < 10; ++i] {
printf["%d ", fib[]];
}
return 0;
}
4, tác vụ đó sẽ trả lại quyền kiểm soát cho HĐH và HĐH sẽ thực thi lệnh hoặc chuyển quyền kiểm soát sang một tác vụ khác trong hàng đợi.Chúng tôi là một bộ lập lịch không ưu tiên, như hình dưới đây, bạn có thể hiểu mối quan hệ giữa chúng.
#include
int fib[] {
static int i, __resume__ = 0;
static a = 0, b = 1, c;
switch [__resume__] {
case 0:
for [i = 0;; ++i] {
if [!__resume__] __resume__ = 1;
c = a + b;
b = a;
a = c;
return a;
case 1:;
}
}
}
int main[] {
for [int i = 0; i < 10; ++i] {
printf["%d ", fib[]];
}
return 0;
}
5 trong HĐH và 0 1 1 2 3 5 8 13 21 34
6 trong Python