2016-07-29 77 views
13

在下面的代碼中,我期望輸出始終爲1,因爲我期望調用poll_one()時只有一個處理程序運行。然而,一次約300次,輸出實際上是3.根據我對boost庫的理解,這看起來不正確。非確定性行爲是錯誤還是預期?io_service :: poll_one非確定性行爲

#include <boost/asio.hpp> 

int main() { 
    boost::asio::io_service io; 
    boost::asio::io_service::work io_work(io); 
    boost::asio::io_service::strand strand1(io); 
    boost::asio::io_service::strand strand2(io); 
    int val = 0; 

    strand1.post([&val, &strand2]() { 
    val = 1; 
    strand2.post([&val]() { 
     val = 2; 
    }); 
    boost::asio::spawn(strand2, [&val](boost::asio::yield_context yield) { 
     val = 3; 
    }); 
    }); 

    io.poll_one(); 
    std::cout << "Last executed: " << val << std::endl; 

    return 0; 
} 

使用升壓ASIO 1.60.0.6

+0

爲什麼要downvote?回答讚賞 –

+0

當然,它是完整的最小和可驗證,並易於編譯。異常不會拋出。 –

+0

如果您將案例數量從3個減少到2個,問題不會出現 –

回答

12

所觀察到的行爲是明確界定,並預期會發生,但我們不應該期望它經常發生。

Asio有一個有限的鏈實現池,默認的分配策略是哈希。如果發生散列衝突,則兩條鏈將使用相同的實現。當散列碰撞發生時,例如簡化爲以下demo

#include <cassert> 
#include <boost/asio.hpp> 

int main() 
{ 
    boost::asio::io_service io_service; 
    boost::asio::io_service::strand strand1(io_service); 
    // Have strand2 use the same implementation as strand1. 
    boost::asio::io_service::strand strand2(strand1); 

    int value = 0; 
    auto handler1 = [&value, &strand1, &strand2]() { 
    assert(strand1.running_in_this_thread()); 
    assert(strand2.running_in_this_thread()); 
    value = 1; 

    // handler2 is queued into strand and never invoked. 
    auto handler2 = [&value]() { assert(false); }; 
    strand2.post(handler2); 

    // handler3 is immediately executed. 
    auto handler3 = [&value]() { value = 3; }; 
    strand2.dispatch(handler3); 
    assert(value == 3); 
    }; 

    // Enqueue handler1. 
    strand1.post(handler1); 

    // Run the event processing loop, executing handler1. 
    assert(io_service.poll_one() == 1); 
} 

在上面的例子:

  • io_service.poll_one()執行單個準備處理程序(handler1
  • handler2永遠不會調用
  • handler3strand2.dispatch()內立即被調用,因爲strand2.dispatch()從處理程序中被調用,其中strand2.running_in_this_thread()個回報true

有利於觀察到的行爲相關的各種細節:

  • io_service::poll_one()將運行io_service的事件循環,也不會妨礙,它最多執行一個準備運行處理器。在dispatch()的上下文中立即執行的處理程序永遠不會入隊到io_service,並且不受poll_one()調用單個處理程序的限制。

  • boost::asio::spawn(strand, function)超載啓動stackful協程AS-如果strand.dispatch()

    • 如果strand.running_in_this_thread()回報false爲調用者,那麼協同程序將被張貼到strand延期調用
    • 如果strand.running_in_this_thread()返回true爲調用者,那麼協程將立即執行
  • 分立strand使用相同實現的對象仍然保持鏈的保證。即,併發執行不會發生,並且定義良好。當離散的strand對象正在使用分立實現,並且多個線程正在運行io_service時,則可以觀察離散鏈併發執行。但是,當離散的strand對象使用相同的實現時,即使多個線程正在運行io_service,也不會觀察併發性。此行爲是documented

    該實現不保證通過不同的strand對象發佈或分派的處理程序將被同時調用。

  • Asio有一個有限的鏈實現池。當前的默認值是193,可以通過將BOOST_ASIO_STRAND_IMPLEMENTATIONS定義爲所需的數字來控制。此功能在Boost.Asio 1.48 release notes

    製造鏈實現通過定義BOOST_ASIO_STRAND_IMPLEMENTATIONS到所需的數字可配置的數目指出。

    通過減小池大小,增加了兩個離散鏈將使用相同實現的機會。使用原始代碼,如果要將池大小設置爲1,則strand1strand2將始終使用相同的實現,導致val始終爲3demo)。

  • 分配鏈實現的默認策略是使用黃金比例散列。使用散列算法時,可能會發生衝突,導致多個分立的對象使用相同的實現。通過定義BOOST_ASIO_ENABLE_SEQUENTIAL_STRAND_ALLOCATION,可以將分配策略更改爲循環,從而防止發生衝突,直到發生分支分配爲止。此功能在Boost.Asio的1.48版本說明註釋:

    BOOST_ASIO_ENABLE_SEQUENTIAL_STRAND_ALLOCATION標誌,該標誌開關鏈實現的分配使用循環的方法,而不是散列增加的支持。

    • strand1strand2具有離散實現
    • io_service::poll_one()執行,其直接貼在單一處理程序:

鑑於上述細節,當1在原始代碼中觀察到發生以下進入strand1

  • 處理程序被張貼到strand1val1
  • 處理程序發佈到strand2入列,亦從未援引
  • 的協同程序的創建被延遲,爲strand的順序調用保證防止協程從已發佈的上一處理程序後,正在創建直到成strand2已執行:

    給予鏈對象s,如果s.post(a)之前發生s.dispatch(b),其中後者的鏈的外部執行,然後asio_handler_invoke(a1, &a1)之前發生asio_handler_invoke(b1, &b1)

  • 在另一方面,當3觀察:

    • 散列碰撞發生時用於strand1strand2,導致它們使用相同的基本鏈實施
    • io_service::poll_one()執行單個處理程序直接發佈到strand1
    • 發佈到012的處理程序套val1
    • 處理程序發佈到strand2入列,亦從未援引
    • 協程立即創建並在boost::asio::spawn()調用,設置val3,作爲strand2可以安全地執行,同時保持的非保障的協同程序併發執行和處理程序調用的順序
    +4

    這個答案是一件藝術品。我喜歡邊緣情況下的小型演示。 +100 AFAIAC – sehe

    +0

    真棒回答。這對於鏈類的文檔是非常有價值的信息。 – hifier

    +0

    @sehe謝謝!多年來,您一直致力於提供優秀的答案,這對我來說是一個巨大的動力。沒有你,這個答案就不會存在。 –