最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

淺談并發(fā)處理PHP進程間通信之外部介質(zhì)

 更新時間:2021年05月26日 11:02:08   作者:枕邊書  
進程間通信(IPC,Inter-Process Communication),多進程開發(fā)中,進程間通信是一個永遠也繞不開的問題。在 web開發(fā)中,我們經(jīng)常遇到的并發(fā)請求問題,本質(zhì)上也可以作為進程間通信來處理。

進程間通信

進程間通信,指至少兩個進程或線程間傳送數(shù)據(jù)或信號的一些技術(shù)或方法。進程是計算機系統(tǒng)分配資源的最小單位(嚴格說來是線程)。每個進程都有自己的一部分獨立的系統(tǒng)資源,彼此是隔離的。為了能使不同的進程互相訪問資源并進行協(xié)調(diào)工作,才有了進程間通信。

根據(jù)定義可知,要進行進程間通信,我們需要解決兩個問題:

  • 互相訪問:消息傳輸和暫時存儲介質(zhì)選擇問題;
  • 協(xié)調(diào)工作:消息的存取沖突問題;

文章介紹的中心就是圍繞著這么兩點來說的, 為了更使文章更簡明,這邊以之前在公司做的一個需求為例:

需要一個循環(huán)ID生成器,循環(huán)生成從 Min 到 Max 的數(shù)字ID,在ID遞增到 Max 后,返回到 Min 重新開始遞增;必須能保證多個進程并發(fā)請求時生成的ID不同。

此需求要解決的問題恰好為我們要解決的進程間通信需要解決的兩個問題:

  • 需要一個消息傳輸通道來傳輸和存儲當前的遞增值。這個比較容易解決,我們常用的文件、數(shù)據(jù)庫、session、緩存等都能做到。
  • 需要解決多進程同時訪問生成器生成相同ID的問題。要滿足這個需要就必須要用到鎖了,而且為了保證多個進程讀取的數(shù)據(jù)是不同的,需要互斥鎖,另外為了能保證調(diào)用成功率,鎖的獲取最好能實現(xiàn)自旋。

本文通過此需求的不同實現(xiàn),來介紹通過外部介質(zhì)進行的進程間通信的方式。另外,不只PHP語言,其他語言也能使用這些方法。

文件

flock

文件是最基本的存儲介質(zhì),它當然可以作為消息的傳輸通道來使用。文件的存取各種語言都有各自的多種方案,問題點是多進程并發(fā)時的沖突問題。

解決存取沖突問題我們使用PHP的flock()函數(shù):

bool flock ( resource $handle , int $operation [, int &$wouldblock ] )

$handler 是 使用fopen($path_to_file)獲取到的文件句柄;

$operation 是 對文件加鎖的方式,有以下值可選:

LOCK_SH (獲取共享鎖) / LOCK_EX (獲取互斥鎖) / LOCK_UN (解鎖)

這里我們選用互斥鎖,一個進程獲取到互斥鎖后,其他進程在嘗試獲取鎖會被阻塞,直到鎖被釋放,即實現(xiàn)了自旋;

此外,還有一個參數(shù) LOCK_NB,flock 在獲取不到鎖時,默認會阻塞住直到鎖被其他進程釋放,傳入 LOCK_NB 與 LOCK_SH 或 LOCK_EX 進行或運算結(jié)果(LOCK_EX | LOCK_NB),flock 在鎖被其他進程占有時,不會阻塞,而是直接返回 false,這里僅作介紹,我們并不使用它。

$wouldblock 參數(shù)是一個引用值,在獲取不到鎖,且不阻塞模式時,$wouldblock 會被設(shè)置為 true;(手冊中說阻塞時才會被設(shè)置為 true。其實我也奇怪這個變量名的。不知道是不是 bug,我的PHP版本是 5.4.5,有知道的煩請解惑)

代碼實現(xiàn)

下面是循環(huán)ID生成器代碼,說明在注釋中:

function getCycleIdFromFile($max, $min = 0) {
    $handler = fopen('/tmp/cycle_id_generator.txt', 'c+');
    if (!flock($handler, LOCK_EX)) {
        throw new Exception('error_get_file_lock!');
    }
    
    $cycle_id = trim(fread($handler, 9));
    $cycle_id++;

    if ($cycle_id > $max) {
        $cycle_id = $min;
    }

    // 文件指針返回到文件頭,并向文件內(nèi)寫入新的cycle_id
    rewind($handler);
    fwrite($handler, $cycle_id);

    // 多寫入一些空格為了防止數(shù)值升到多位后,突然置為少位后面的數(shù)字仍保留
    fwrite($handler, str_repeat(' ', 9));

    flock($handler, LOCK_UN);

    return $cycle_id;
}

mysql

select for update

我們常用的 mysql 也可以被當作中間介質(zhì)來實現(xiàn)進程間的通信,我們規(guī)定好某一個數(shù)據(jù)表內(nèi)的某一行數(shù)據(jù)作為消息交換的中轉(zhuǎn)站,使用 mysql 自帶的鎖來協(xié)調(diào)多個進程的存取沖突。

事務(wù)的設(shè)計目的就是為了解決多進程并發(fā)查詢時數(shù)據(jù)沖突的問題,可是我們常用的事務(wù)只能保證數(shù)據(jù)沖突時會被回滾,數(shù)據(jù)不會出現(xiàn)錯誤,并不能實現(xiàn)請求的并行化。對一些數(shù)據(jù)沖突回滾的請求,需要我們在外層添加邏輯重試。

這里介紹 mysql 的一種語法: select for update,會給固定數(shù)據(jù)加上互斥鎖,且另一個請求在獲取鎖失敗時,會阻塞至獲取鎖成功,mysql 幫我們實現(xiàn)了自旋;

用法如下:

1.關(guān)閉 mysql 的自動提交,自動提交默認打開,除非使用 transition 語句顯示開啟事務(wù),默認會將每一條 sql 作為一個事務(wù)直接提交執(zhí)行,這里關(guān)閉。 set autocommit=0;

2.使用select for update 語句給數(shù)據(jù)添加互斥鎖。注意:需求 mysql 的 innodb 引擎支持;

3.進行數(shù)據(jù)更新和處理操作;

4.主動提交事務(wù),并將 自動提交恢復(fù);commit; set autocommit=1;

代碼實現(xiàn)

然后是代碼實現(xiàn):

// 數(shù)據(jù)庫連接實現(xiàn)各有不同,demo 可以自己修改一下。
function getCycleIdFromMysql($max, $min = 0){
    Db::db()->execute('set autocommit = 0');
    $res = Db::db()->qsqlone('SELECT cycle_id FROM cycle_id_generator WHERE id = 1 FOR UPDATE');

    $cycle_id = $res['cycle_id'] + 1;
    if($cycle_id > $max){
        $cycle_id = $min;
    }

    Db::db()->execute("UPDATE cycle_id_generator SET cycle_id = {$cycle_id} WHERE id = 1");

    Db::db()->execute('commit');
    Db::db()->execute('set autocommit = 1');

    return $cycle_id;
}

redis

incr

redis 是我們常用的緩存服務(wù)器,由于其使用內(nèi)存存儲數(shù)據(jù),性能很高。我們使用一個固定的普通鍵來作為消息中轉(zhuǎn)站,然后利用其incr命令的原子性和其執(zhí)行結(jié)果(遞增后的值),實現(xiàn) cycle_id 的遞增。

incr(key) 若 key 不存在,redis 會先將值設(shè)置為0,然后執(zhí)行遞增操作;

遞增沒有問題,可是我們還有個需求是在要其值達到 max 時,再將其置為 min,這時就可能會出現(xiàn)進程A在更新值為 min 時,另一個進程B也檢測到值大于了 max,然后將值置為 min,可是這時的值已經(jīng)不是 max,即發(fā)生了值重復(fù)更新,那么返回的值必然會有重復(fù);

這時,我們就需要自己來實現(xiàn)鎖了。

SETNX

redis 的 SETNX 命令檢測某一個 key 是否存在,若不存在,則將 key 的值設(shè)置為 value,并返回結(jié)果1; 若 key 已存在,則設(shè)置失敗,返回值0。

SETNX key value

它能實現(xiàn)鎖是因為它是一個原子命令,即 檢測 key 是否存在和設(shè)置 key 值在一個事務(wù)內(nèi),不會出現(xiàn)同時兩個進程都檢測到 key 不存在,然后同時去設(shè)置 key 的情況。

我們以另一個值的存在與否,來表示 cycle_id 是否正在被另一個進程修改。

代碼實現(xiàn)

 function getCycleIdFromRedis($max, $min = 0) {
    $redis = new Redis();
    $redis->connect('127.0.0.1', 6379);
    $key_id = 'cycle_id_generator';

    $cycle_id = $redis->incr($key_id);
    
    if ($cycle_id > $max) {
        // 設(shè)置"鎖鍵"的結(jié)果 = 獲取互斥結(jié)果
        $key_lock = 'cycle_id_lock';
        if (!$redis->setnx($key_lock, 1)) {
            return null;
        }

        $cycle_id = $min;
        $redis->set($key_id, $cycle_id);

        // 最后別忘記釋放互斥鎖
        $redis->delete($key_lock);
    }

    $redis->close();

    return $cycle_id;
}

注意:由于 redis 里沒有能實現(xiàn)自旋鎖的命令,如果需求最高的獲取成功率,我們在檢測到 cycle_id 已經(jīng)是最大值,且試圖修改獲取鎖失敗時,退出重試,在外層進行重試。

function getCycleId($max, $min = 0) {
    $cycle_id = getCycleIdFromRedis($max, $min);
    if (!is_null($cycle_id)) {
        return $cycle_id;
    }
    // 稍微等待下正在更改的進程
    usleep(500);
    // 這里使用遞歸,直至獲取成功  并發(fā)很高,cycle_id重置很頻繁時慎用.
    return getCycleId($max, $min);
}

優(yōu)化

審查代碼我們會發(fā)現(xiàn),如果 max-min 的值很小的話,redis 會需要經(jīng)常重置 key 的值,也就經(jīng)常需要加鎖,重試也就很多。這里,我提供一個優(yōu)化方法:

我們將其 max 設(shè)置為一個很大的值(要能被 max-min 整除),返回值時稍做處理,返回 $current % ($max - $min) + $min;。這樣,key 需要遞增到一個很大的值才會被重置,加鎖邏輯和外層邏輯會很少執(zhí)行到,達到提升效率的目的。

總結(jié)

這里簡單的評價一下上面所說的三種方法:

性能上沒有測試,而且 redis 的性能跟 ID 的大小差值相關(guān),不過猜測在ID大小差值大的情況下 redis 應(yīng)該更好一點。

代碼上非常直觀,使用 mysql 非常簡潔,而且 redis 要自己實現(xiàn)自旋,比較惡心。

實現(xiàn)上,當然是文件最為方便,無任何添加。

以上就是淺談并發(fā)處理PHP進程間通信之外部介質(zhì)的詳細內(nèi)容,更多關(guān)于并發(fā)處理PHP進程間通信之外部介質(zhì)的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • php7 list()、session及其他模塊的修改實例分析

    php7 list()、session及其他模塊的修改實例分析

    這篇文章主要介紹了php7 list()、session及其他模塊的修改,結(jié)合實例形式分析了php7 list()、session及其他模塊的修改相關(guān)操作技巧與使用注意事項,需要的朋友可以參考下
    2020-05-05
  • 簡單的方法讓你的后臺登錄更加安全(php中加session驗證)

    簡單的方法讓你的后臺登錄更加安全(php中加session驗證)

    通過特定文件為后臺入口注冊session,否則失敗退出。即直接使用原后臺地址將無法登錄后臺。這樣一來,入口文件名的多樣性、可變更性將為你的后臺登錄提供更加安全的環(huán)境
    2012-08-08
  • php防止用戶重復(fù)提交表單

    php防止用戶重復(fù)提交表單

    這篇文章主要介紹了解決php表單重復(fù)提交實現(xiàn)方法,需要的朋友可以參考下
    2015-11-11
  • PHP實現(xiàn)補齊關(guān)閉的HTML標簽

    PHP實現(xiàn)補齊關(guān)閉的HTML標簽

    這篇文章主要介紹了PHP實現(xiàn)補齊關(guān)閉的HTML標簽的方法,通過自定義函數(shù)實現(xiàn)針對HTML標簽的閉合補全功能,涉及PHP字符串及正則匹配相關(guān)操作技巧,需要的朋友可以參考下
    2016-03-03
  • php連接oracle數(shù)據(jù)庫的方法(測試成功)

    php連接oracle數(shù)據(jù)庫的方法(測試成功)

    這篇文章主要介紹了php連接oracle數(shù)據(jù)庫的方法,簡單分析了php連接Oracle數(shù)據(jù)庫的常見方法與具體操作技巧,并對可能出現(xiàn)的問題進行了總結(jié)分析,需要的朋友可以參考下
    2016-05-05
  • PHP strip_tags保留多個HTML標簽的方法

    PHP strip_tags保留多個HTML標簽的方法

    這篇文章主要介紹了PHP strip_tags保留多個HTML標簽的方法,需要的朋友可以參考下
    2016-05-05
  • php中分頁及SqlHelper類用法實例

    php中分頁及SqlHelper類用法實例

    這篇文章主要介紹了php中分頁及SqlHelper類用法,結(jié)合實例形式分析了php數(shù)據(jù)庫查詢類與分頁類的定義與具體使用技巧,需要的朋友可以參考下
    2017-01-01
  • PHP創(chuàng)建桌面快捷方式的實例代碼

    PHP創(chuàng)建桌面快捷方式的實例代碼

    本篇文章主要是對PHP創(chuàng)建桌面快捷方式的實例代碼進行了介紹,需要的朋友可以過來參考下,希望對大家有所幫助
    2014-02-02
  • php文件緩存類用法實例分析

    php文件緩存類用法實例分析

    這篇文章主要介紹了php文件緩存類用法,以實例形式較為詳細的分析了php文件緩存類的定義、功能及具體使用技巧,非常具有實用價值,需要的朋友可以參考下
    2015-04-04
  • php 生成短網(wǎng)址原理及代碼

    php 生成短網(wǎng)址原理及代碼

    將原網(wǎng)址做crc32校驗,得到校驗碼,使用sprintf將校驗碼轉(zhuǎn)為無符號數(shù)字,詳細步驟請看本文
    2014-01-01

最新評論

彩票| 新兴县| 师宗县| 温州市| 荔浦县| 泸水县| 会东县| 色达县| 钟祥市| 三明市| 崇明县| 额济纳旗| 扬中市| 松阳县| 大庆市| 湄潭县| 资阳市| 兴国县| 定襄县| 双桥区| 连江县| 白朗县| 枣庄市| 石嘴山市| 泾阳县| 株洲县| 柏乡县| 垫江县| 苏尼特左旗| 米易县| 阿鲁科尔沁旗| 龙井市| 边坝县| 卓资县| 新宁县| 当雄县| 偏关县| 泰和县| 汨罗市| 乐山市| 咸阳市|