Con trăn không đồng bộ là gì?

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ự

Cấu trúc của chương trình không giống nhau

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
0

Khi 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

Chương trình con so với Coroutine

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átYesYesNoYesNo

câ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
5

Và ở 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 vs Chủ đề

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ùng

Sau đó, 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
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

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

Lậ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

Chế độ xử lý dữ liệu

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ý

Thống kê IP

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

Chú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

Sự kết hợp giữa coroutine và thread

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

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

Oh, 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ười

Lư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

    #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 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

3. Bộ lập lịch hệ điều hành nóng

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

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

Chủ Đề