Boost.Asio基礎(五) 非同步程式設計初探

weixin_34377065發表於2016-02-05

非同步程式設計

       本節深入討論非同步程式設計將遇到的若干問題。建議多次閱讀,以便吃透這一節的內容,這一節是對整個boost.asio來說是非常重要的。

為什麼須要非同步

       如前所述,通常同步程式設計要比非同步程式設計更簡單。同步程式設計下,我們非常easy線性地對問題進行考量。函式A呼叫完,繼續執行B。B執行完,繼續執行C。以此類推。相對照較直觀。而對於非同步程式設計,如果有5個事件,我們非常難知道它們詳細的執行順序,你甚至不知道,它究竟會不會被執行。
儘管編寫非同步的程式,非常難,可是依舊須要使用這樣的方法。

由於server程式須要同一時候並行的處理大量的客戶端。並行的客戶端越多,非同步程式設計的優勢就越明顯。
        如果有一個server程式,須要同一時候處理1000個並行的客戶端,客戶端和server之間的通訊訊息。以’\n’來結尾。
這是同步模式下的程式碼,使用1個執行緒:

using namespace boost::asio;
struct client{
    ip::tcp::socket sock;
    char buff[1024]; //每一個訊息最大1024個位元組
    int already_read; //已經讀取了多少位元組
};
std::vector<client> clients;
void handle_clients() {
    while(true) {
        for(int i=0; i<clients.size(); ++i) {
            if(clients[i].sock.available() ) on_read(clients[i]));
        }
    }
}

void on_read(client& c) {
    int to_read = std::min(1024 - c.already_read, c.sock.available());
    c.sock.read_some(buffer(c.buff + c.already_read, to_read);
    c.already_read += to_read;
    if(std::find(c.buff, c.buff + c.already_read, '\n') < c.buff + c.already_read) {
        int pos = std::find(c.buff, c.buff + c.alread_read, '\n') - c.buff;
        std::string msg(c.buff, c.buff + pos);
        std::copy(c.buff + pos, c.buff + 1024, c.buff);
        c.already_read -= pos;
        on_read_msg(c, msg);
    }
}

void on_read_msg(client & c, const std::string& msg) {
    if(msg == "request_login")
        c.sock.write("request_ok\n");
    else if ...
}

     在任務server程式中,你都須要避免程式碼被堵塞。看上面的程式碼,我們希望handle_clients()這個函式儘可能的不被堵塞。

不論什麼時候函式堵塞。從客戶端發來的訊息就會等待。直到函式不堵塞的時候才幹來處理它們。為了避免程式被堵塞,我們僅僅在socket中確實有資料的時候才去讀取,實現的程式碼是:

if(clients[i].sock.available() ) on_read(clients[i]));

在on_read函式中,我們僅僅讀取socket確實有效的資料;呼叫read_util(c.sock, buffer(…),’\n’);是非常不好的選擇。它會堵塞程式,直到完整的讀取了所有的資料才返回。

我們永遠不知道究竟什麼時候才幹讀取完成。


     程式的瓶頸是on_read_msg()這個函式。所有輸入的資料都被卡在這裡。on_read_msg()應該儘量避免出現這樣的情況,但有時這又是全然沒辦法避免的。(比方,當socket的緩衝區滿了以後,操作必將被堵塞)。
     以下是同步模式下的程式碼,使用10個執行緒:

using namespace boost::asio;
    //...這裡的定義和前面一樣
    bool set_reading() {
        boost::mutex::scope_lock lk(cs_);
        if(is_reading_) return false;
        else { if_reading_ = true; return true; }
    }
    void unset_reading() {
        boost::mutex::scoped_lock lk(cs_);
        is_reading_ = false;
    }
    private:
        boost::mutex cs_;
        bool is_reading_;
};
std::vector<client> clients;
void handle_clients() {
    for(int i=0; i<10; ++i) {
        boost::thread(handle_clients_thread);
    }
}

void handle_clients_thread() {
    while(true) {
        for(int i=0; i<clients.size(); ++i) {
            if(clients[i].sock.available()) {
                if(clients[i].set_reading()) {
                    on_read(clients[i]);
                    clients[i].unset_reading();
                }
            }
        }
    }
}

void on_read(client & c) {
...
}
void on_read_msg(client & c, const std::string& msg) {
...
}

     為了使用多執行緒,我們須要同步機制,set_reading()和unset_reading()就是在做這個用的。set_reading()。非常重要。它測試當前是否有執行緒在做讀取操作。
     能夠看出來程式碼已經比較複雜了。
     另一種同步程式設計的方法,就是每一個client分配一個執行緒。可是隨著並行的客戶端數量的增長,這顯然是不可行的。


     如今我們來看看非同步方法。當client請求資料的時候,on_read被呼叫,傳送返回資料,之後又等待下一個請求(執行另一個非同步的讀操作)。


非同步程式碼,10個執行緒:

using namespace boost::asio;
io_service service;
struct client {
    ip::tcp::socket sock;
    streambuf buff;
};
std::vector<client> clients;
void handle_clients() {
    for(int i=0; i<clients.size(); ++i) {
        async_read_util(clients[i].sock, clients[i].buff, '\n',
            boost::bind(on_read, clients[i], _1, _2));
    }
    for(int i=0; i<10; ++i)
        boost::thread(handle_clients_thread);
}
void handle_clients_thread() {
    service_run();
}

void on_read(client& c, const error_code& err, size_t read_bytes) {
    std::istream in(&c.buff);
    std::string msg;
    std::getline(in, msg);
    if(msg == "request_login")
        c.sock.async_write("request_ok\n", on_write);
    else if ...
    ...
    //如今在同一個socket上等待下一個讀取請求
    async_read_util(c.sock, c.buff, '\n',
        boost::bind(on_read, c, _1, _2));
}

相關文章