2016年1月21日 星期四

Loopback 妙用: 應用篇

在 Loopback 妙用一文中有網友問這有哪些應用,這邊就來舉兩個例子

1. 安全的結束 thread

現代軟體除了系統給予的 main thread 外,通常還會自己產生一些 thread(s),如何安全的結束這些額外產生的 thread(s) 就成了一項重要的課題。最常見的方法就是用旗標,就算您不懂 Qt,下面的程式也能懂個大概(這樣的程式大概也出現在很多軟體公司):


//mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>

class MyThread : public QThread
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = 0);
    void stop();
signals:
    
public slots:

protected:
    void run();

private:
    bool m_stop; //the flag for stop thread
    
};

#endif // MYTHREAD_H

//mythread.cpp
#include "mythread.h"

MyThread::MyThread(QObject *parent) :
    QThread(parent),
    m_stop(false)
{
}


void MyThread::run()
{
    while(!m_stop)
    {
        //......
    }
}


void MyThread::stop()
{
    m_stop = true;
}


這樣的作法只能算勉強及格,因為以現代 CPU 的性能來說,通常 thread 不會卡在計算裡,多半是被阻塞(blocking)在 I/O 裡,所以 thread 要偵測到旗標得等到 I/O 完成才有機會,以 Winsock 來說就是要等到 send()/recv() 完成或是 timeout。

既然我們的問題是 thread 要等到 send()/recv() 完成或 timeout 才能往下執行,那如果有一組可以偵測多個 socket handle(s) 的 API(select),那我們就可以開啟一個專用通道通知 thread 去領便當了。


不過 Windows 麻煩的地方就是 select() 只能用在 winsock 上,如果要用在其他種類的 I/O 上還是得回頭用 WaitForMultipleObjects,好在站長已經寫了超清楚的文章解釋用法與陷阱,請多加利用。

2. 限制資源存取

假設你有一個專案,一開始只有一個 thread 負責讀寫檔案:

隨著專案成長,thread 開始變多,也想存取這個檔案(這很常見,比方說這個檔案是種檔案型資料庫如 SQLite)。比較妥當的方式是透過原來的 thread A 來存取:

為什麼不乾脆用個簡單的 mutex 把檔案保護算了?有幾個理由:
  1. 不想讓檔案的每一部份都變成可見(不過可靠精心設計的 API 達成)
  2. 無論讀寫都使用同一把鎖(mutex),如果大部分的 thread(s) 只有讀將會效率不彰。
理由 2. 有一個作法是使用 Multi-Read/Single Write 的技巧迴避(例如 Qt 的 QReadWriteLock )這個技巧會允許多個 thread 同時讀取,但寫入時只允許同一個 thread  進入,但這個作法有一個風險就是可能那個想寫入的 thread 陷入飢餓的狀態(一直輪不到他)。

但 select() 沒有這樣的風險,除非你刻意忽略 OS 傳來的訊號(由 select() fd_set 參數),否則一定會處理(誰先誰後而已),而且因為沒有 mutex 與其他奇門遁甲的上鎖機制,犯錯空間極少。


但這邊還是沒講清楚如何讓 thread 同時讀取 File,畢竟 select() 回傳後還是要循序處理。對此問題我們可以加上快取層,thread B/C/D 只有在 cache 找不到資料時才會請求 thread A 處理,而 thread A 一旦取得資料就放入 cache,於是 thread B/C/D 下次就不用透過通訊的方式查詢資料。


事實上,流行的 PHP + MySQL + Memcached 就這樣玩的,PHP 把欲查詢 MySQL database 的 SQL 指令先以單向雜湊函數(如 MD5)演算過後成為一把獨一無二的 key,丟進 Memcached 看看有沒有已經快取的資料,如果沒有才真的跟 MySQL 要。


Memcached 最大用戶其實就是大家每天在用的 Facebook,光是 2008 的資料就顯示 fb 用了 800 台伺服器、28 TB 記憶體(光是這 800 台伺服器如何維護、備援、同步就是不小的學問)。

另外快取層顯然還是要用到 mutex,任何一本講多工程式撰寫的書都會告訴你在被 mutex 保護的 critical section 在裡面待越短暫越好,那電腦上只有一種裝置可以滿足這種特性:那就是主記憶體,其他恐怕通通不及格,至於通訊更是要不得,這也是 nginx、lighttp、node.js 幾乎全盤放棄 thread 的原因。

希望本篇文章對您有幫助 :)


2 則留言:

  1. Hi 版大你好,

    另外快取層顯然還是要用到 mutex
    => 這邊是不是指說Thread A 更新至Cache之前也是需要做Mutex,避免Thread B ~ C 會有read錯誤的風險?

    謝謝,
    GJ 2018/01/02

    回覆刪除
    回覆
    1. 可以參考一下這篇,我建議使用 Cache Aside Pattern

      https://coolshell.cn/articles/17416.html

      如國要完全保持同步,個人知道的方法就是
      Single Write/Multiple Read Lock/Unlock機制

      不過碰上大量thread同時寫入時又會回到效率下降的問題,恐怕唯一解法就是如 Cache Aside Pattern,用資料不同的機率做代價交換

      刪除