Không đồng bộ/chờ yêu cầu python

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 bên dưới cho chúng ta thấy một số giải pháp cho các vấn đề về bất đồng bộ

Hệ thống sinh thái của bất đồng bộ lập trình

Lưu ý, các luồng trong Python là các luồng gốc, nhưng thực hiện một số chính sách [cụ thể là trong bản cpython], GIL [Khóa phiên dịch toàn cầu] sẽ không cho phép chúng chạy 2 luồng đồng thời. Do đó, các thread Python không thực sự xử lý song song và thật sự mình không thích sử dụng chúng làm

  • Duy trì tài nguyên
  • Ngã ba một chủ đề mới là cực kỳ thời gian

Chúng ta có thể thấy rằng, các luồng và quy trình đều sở hữu một khoảng không gian bộ nhớ của riêng nó, do đó, chúng có thể thực hiện các công việc độc lập với luồng chính hoặc quy trình chính

Trái lại, vòng lặp sự kiện duy trì các nhiệm vụ, các nhiệm vụ này chia sẻ bộ nhớ chung và chúng ta phải trả lời câu hỏi.

0 1 1 2 3 5 8 13 21 34
0

Khi nào chúng ta sử dụng event loop, thread hay process?

Trong khoa học máy tính, chúng ta có thể phân tác vụ thành 2 loại

  • Ràng buộc bởi CPU

    Trong khoa học máy tính, máy tính bị ràng buộc bởi CPU [hoặc giới hạn máy tính] khi thời gian để nó hoàn thành một nhiệm 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 bộ xử lý cao, có thể ở mức sử dụng 100% trong nhiều giây hoặc phút. Các ngắt do thiết bị ngoại vi tạo ra có thể được xử lý chậm hoặc bị trì hoãn vô thời hạn

  • Ràng buộc bởi I/

    Ràng buộc I/O đề cập đến điều kiện trong thời gian đó cần thiết để hoàn thành một yếu tố chủ quyền được phép đã được xác định trong khoảng thời gian chờ các hoạt động vào/ra được hoàn thành. Điều này đảo ngược với một nhiệm vụ bị ràng buộc bởi CPU. Trường hợp này phát sinh khi tốc độ dữ liệu được lấy chậm hơn tốc độ nó được sử dụng hoặc nói theo cách khác, dành nhiều thời gian hơn để lấy dữ liệu hơn là xử lý nó

Ví dụ

  • sử dụng đa xử lý [luồng gốc] cho liên kết I/O

  • sử dụng multi process [or the native thread] cho các tác vụ bị ràng buộc bởi I/O

    Jason hỏi David rằng 'Hôm nay bạn làm gì?'

    Không, nó không phải là một giải pháp tối ưu. 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 chưa đạt được, sửa lại nó đi' 😥

    Chính xác đây là cách làm việc của vòng lặp sự kiện đối với nhiệm vụ bị ràng buộc bởi I/O

  • sử dụng vòng lặp sự kiện cho các nhiệm vụ bị ràng buộc bởi CPU

    Nhóm 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 nên David làm một nhiệm vụ trong 1 giờ và chuyển qua nhiệm vụ khác. Làm đó, cuối tuần, anh ấy cống hiến sức lực và vất vả 😷

    Không, chúng ta có tận hưởng 5 thành viên, tại sao chúng ta lại cống hiến hết công việc cho David?

    Đây là cách xử lý lí đa tiến trình thực thi

Qua ví dụ trên, ta tháy rằng vòng lặp sự kiện chỉ thực sự hữu dụng trong vấn đề liên quan đến IO bound. 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 có thể là giải pháp tốt nhất cho các vấn đề liên quan đến IO bound

Các vấn đề trong vòng lặp sự kiện mô hình

Chúng ta đã biết rằng chúng ta có một ngữ cảnh của một hàm bất kỳ. Ngữ cảnh này bao gồm các biến và chúng được giải phóng sau khi hàm kết thúc [giải phóng ra khỏi ngăn xếp]

Trong tác vụ giới hạn I/O, chúng ta sẽ có một vài lệnh lấy dữ liệu [thao tác IO] mà lúc đó chúng ta cần tối ưu hóa [chẳng hạn như ví dụ về David, anh ta có thể tạm dừng các tác vụ đang chờ kiểm tra và

Như vậy các ngắt là một vấn đề, làm sao chúng ta có thể tạo ra các ngắt trong hàm mà vẫn giữ nguyên ngữ cảnh của hàm để có thể thực thi tiếp?

Tại các ngắt đó, hàm đang thực thi[callee] cần trao lại quyền điều khiển chương trình [program control] cho nơi đã gọi hàm đó [gọi], ở đây thực chất là vòng lặp sự kiện và chúng ta cũng cần bắt đầu tại điểm

Giải pháp ở đây là sử dụng coroutine

Coroutine là gì?

Donald Knuth nói rằng

Chương trình con là trường hợp cơ bản của chương trình con

Đúng vậy, tổng quát hóa, các hàm bình thường chúng ta hay sử dụng [hàm được giải nén ngữ cảnh sau khi thoát khỏi hàm] là trường hợp đặc biệt của coroutine - nơi ngữ cảnh đó có thể được giữ lại khi nó được sử dụng tạm thời

Chương trình con vs Coroutine

Tại sao coroutine lại sở hữu ứng dụng cho hệ thống sự kiện?

  • là lập lịch không ưu tiên
  • có thể tạm dừng và tiếp tục ở bất kỳ đâu do đó, nếu dữ liệu là luồng, chúng tôi có thể tiếp tục bộ nhớ
  • could duy trì trạng thái
  • với ràng buộc về I/O, coroutine bộ nhớ tối ưu và CPU
  • chúng tôi thu nhỏ

Đơn vị làm việc

ProcessNative threadLuồng xanhGoroutineCoroutineBộ nhớ≤ 8Mb≤ Nx2Mb≥ 64Kb≥ 8Kb≥ 0MbQuản lý bởi OSYesYesNoNoNoKhông gian địa chỉ riêngYesNoNoNoNoPre-emptive scheduleCóYesNoNoKhả năng bài hátCóYesNoYesNo

Câu hỏi là. Vậy coroutine làm việc như thế nào?

Làm thế nào để cài đặt một coroutine?

Về cơ bản, nó cố gắng lưu lại 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 đóng vai trò như là ngắt. Trước khi tạm dừng hàm[tạm dừng], biến
0 1 1 2 3 5 8 13 21 34
2 được đặt là đ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à cách mà 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
5

Và ở bên dưới, nó được chuyển sang mã Python từ mã C ở trên

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 nên xem kết nối quản trị như thế này

0 1 1 2 3 5 8 13 21 34

Mình có thể build bất kỳ coroutine nào trong C. Bạn có thể làm được điều đó khô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;
}
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ể 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 khoảng trống bộ nhớ tĩnh để lưu lại trạng thái khi nó tạm dừng và khôi phục lại mà không bị mất ngữ cảnh. Trong C, không gian tĩnh là các biến tĩnh, chúng được duy trì bởi hệ điều hành khi một hàm thoát. Trong Python, ngữ cảnh của hàm được lưu trữ trong các khung ngăn xếp

Hãy nghĩ về các quy trình như là các giai đoạn của một chương trình, không có bộ nhớ riêng, không thực hiện bài hát và cực kỳ an toàn

Coroutine vs Chủ đề

Coroutine giảm thiểu các lỗi do lí đa tiến trình [đa luồng] gây ra và mình nghĩ nó là giải pháp tốt nhất cho các nhiệm vụ liên quan đến mạng bởi nó chỉ tồn tại trong 1 tiến trình

Trong Python, chúng ta có thể định nghĩa coroutine bằng việc sử dụng lệnh

0 1 1 2 3 5 8 13 21 34
6 trong định nghĩa hàm. Khi chúng ta gọi hàm, chúng ta trả về một coroutine thay vì kết quả cuối cùng

Sau đó, kết quả có thể được tìm thấy

Starting
Consume data
Hello World

Trình tạo là một trường hợp đặc biệt của coroutine, chúng chỉ có thể sinh [sản xuất] dữ liệu mà không thể tiêu 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[]]

And here is results

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

Chúng ta có thể cấu trúc lại đoạn mã với

0 1 1 2 3 5 8 13 21 34
7

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=' ']
0

Xây dựng cây nhị phân với
0 1 1 2 3 5 8 13 21 34
7

Build nhị phân cây với
0 1 1 2 3 5 8 13 21 34
9

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 bất đồng bộ

Trong trường hợp này, máy chủ TCP là hệ thống sự kiện

  • nguồn sự kiện. socket listening và socket connect

  • về cơ bản, chúng ta có hai dạng sự kiện. EVENT_READ, EVENT_WRITE

  • and task are the coroutine, each task will handle 1 event of 1 connection at 1 time point.

  • Chúng ta cũng có một vòng lặp sự kiện, nó là một bộ ghép kênh I/O cho bộ mô tả tệp

You can run it

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=' ']
3

Trình thiết lập lịch và nhiệm vụ trong các thư viện thực tế.

2. hệ thống truyền phát

Chúng ta có thể sử dụng coroutine để 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ỏ. We are set to coroutine with context riêng. Bạn có thể nhìn thấy chúng trong hình bên dưới

Mô hình xử lý dữ liệu miễn phí

Nguồn sự kiện có thể là Redis pub/sub, Kafka, RabbitMQ hoặc người dùng tương tác,

Chúng ta có thể mô tả bất kỳ loại hệ thống nào nếu chúng ta tạo ra một khối logic cụ thể. khối lọc dữ liệu, khối điều kiện, bộ chọn, khối quảng bá

Ví dụ. xây dựng một bộ phân tích địa chỉ truy cập IP của một máy chủ web

Đầu tiên, bạn cần có một tệp nhật ký dữ liệu

Thống kê IP

Sau đó chạy

Kết quả có thể 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=' ']
4

Cải thiện

Chúng ta có thể bọc các thread trong một coroutine, tại sao không?

Đơn giản, chúng ta sử dụng các chủ đề thay cho các máy

OK, hãy thiết kế tại cái sơ đồ nào

Kết hợp coroutine và thread

Trong sơ đồ trên, mình chuyển các logic vào trong các luồng và sử dụng các hàng đợi như là kênh giao tiếp với các luồng

Không chỉ vậy, hàng đơi còn đóng vai trò như bộ đệm nếu tốc độ bắt đầu lớn hơn tốc độ đầu ra của đơn vị xử lý lý do đó

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=' ']
5

And run it as after

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=' ']
6

Oh, in out results?

Mình cho các bạn gợi ý, hay thêm câu 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 câu 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 sao. nụ cười

Lưu ý

  • Khi chúng ta sử dụng coroutine, chúng ta nên xem xét coroutine có thể bị quá tải hay không. Có nghĩa là, tại một thời điểm, coroutine that can be medium data into, just used to be used to be used in the it or not?

  • Tránh các thiết kế DAG

  • 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 luồng đồng bộ, ý mình là 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 luồng

3. Set up the history for OS

Bộ lập lịch hệ thống vận hành

Khi một câu lệnh trong 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 điều khiển cho HĐH và HĐH thực thi lệnh hoặc chuyển quyền điều khiển cho tác vụ khác trong hàng đợi

Nó là một bộ lập lịch không ưu tiên, qua ví dụ dưới đây, các bạn có thể hiểu được mối liên hệ giữa.

#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 OS và
0 1 1 2 3 5 8 13 21 34
6 trong Python

Chủ Đề