對lua協程的一點理解


讀《Programming In Lua》協程那一章,比較困惑的還是procuer-consumer那個例子:

function consumer(prod)
    while true do
        local x = receive(prod)
        print(x)
    end
end

function receive(prod)
    local status, value = coroutine.resume(prod)
    return value
end

function send(x)
    coroutine.yield(x) -- go back to where resumed
end

function producer()
    return coroutine.create(function()
        while true do
            local x = io.read()
            send(x)
        end
    end)
end

-- consumer-driven design
consumer(producer())

producer產生數據,consumer消費數據,producer與consumer都在各自的協程中完成, 代碼很短,但是很難讀 - 至少不是那么一目了然,尤其比起這個直接的循環:

function produce()
    return io.read()
end

function consume(x)
    print(x)
end

while true do
    local x = produce()
    consume(x)
end

好在哪里?

書中說可以添加緩存控制速度,或者進行數據過濾 - 但是這在循環版本的producer-consumer中也都能做到,無非在在實現produce是多些邏輯,或者再加個filter的函數處理produce的返回值,協程版本毫無優勢可言。

實在要說其優點,恐怕只是:producer和consumer的代碼在各自的協程中實現,並通過resume-yield交換數據 - 實現了松耦合。這是個優點,可以還是不太滿意,再往下看時,看到了全排列那個例子,稍作修改,讓我比較直觀的感覺到了協程這種控制結構的靈活性:

function send(a)
    coroutine.yield(a)
end

function receive(prod)
    local status, value = coroutine.resume(prod)
    return value
end

function consumer(prod)
    local function print_result(a)
        for _, v in ipairs(a) do
            io.write(v, " ")
        end
        io.write('\n')
    end
    
    while true do
        local a = receive(prod)
        if not a then break end
        print_result(a)
    end
end


function producer(a)
    function permgen(a, n)
        if n == 0 then
            send(a) -- send something for consuming from an recursive call
        else
            for i=1, n do
                a[n], a[i] = a[i], a[n]
                permgen(a, n-1)
                a[n], a[i] = a[i], a[n]
            end
        end
    end

    local n = table.getn(a)
    return coroutine.create(function() permgen(a, n) end)
end

consumer(producer({1,2,3,4}))

這里全排列采用了遞歸算法:對數組中的每個元素,把它交換到數組末尾,然后對前n-1個元素的子數組做同樣的事,當n==0時,輸出一個排列。

這個在produce的時候,因為在一個遞歸調用中,你無法一個個返回:

  • 要么每找到一個,直接在里面處理 - 這樣produce-consume的結構就有點混淆在一起;
  • 或者先把結果保存在一個共享內存中,產生全部排列后,再逐個處理 - 這樣要另外開辟空間,流程上感覺也不夠簡潔。

於是,協程的有點就顯現出來了,不同於簡單函數中的return,協程可以在找到一個排列后,yield掛起本協程並返回該排列,返回到原來resume這個協程的代碼處,取得數據進行consume,然后繼續resume進入協程獲取排列 - 通過協程靈活控制流程並傳遞數據,十分漂亮。

所以,那個循環版本的問題是:並不是所有produce的數據,都可以簡單的return回來的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM