2016-10-27 14 views
14

Chúng tôi đã sử dụng asio trong sản xuất trong nhiều năm nay và gần đây chúng tôi đã đạt đến điểm quan trọng khi máy chủ của chúng tôi chỉ nạp đủ để nhận thấy vấn đề bí ẩn.boost :: asio lý do đằng sau num_implementations cho io_service :: strand

Trong kiến ​​trúc của chúng tôi, mỗi thực thể riêng lẻ chạy độc lập sử dụng đối tượng strand cá nhân. Một số thực thể có thể thực hiện một công việc dài (đọc từ tệp, thực hiện yêu cầu MySQL, v.v.). Rõ ràng, công việc được thực hiện trong xử lý được bọc bằng sợi. Tất cả âm thanh đẹp và đẹp và nên hoạt động hoàn hảo, cho đến khi chúng tôi bắt đầu nhận thấy một điều không thể như hẹn giờ hết hạn sau vài giây, mặc dù chủ đề 'chờ công việc' và ngừng hoạt động vì không có lý do rõ ràng. Nó trông giống như công việc dài được thực hiện bên trong một sợi đã ảnh hưởng đến các sợi không liên quan khác, không phải tất cả chúng, nhưng hầu hết.

Đã vô số giờ để xác định vấn đề. Đường đi đã dẫn đến cách đối tượng strand được tạo: strand_service::construct (here).

Vì một số lý do, nhà phát triển đã quyết định có một số lượng giới hạn triển khai strand. Có nghĩa là một số đối tượng hoàn toàn không liên quan sẽ chia sẻ một triển khai đơn lẻ và do đó sẽ bị tắc nghẽn vì điều này.

Trong phương thức độc lập (không tăng cường) asio thư viện, cách tiếp cận tương tự đang được sử dụng. Nhưng thay vì triển khai được chia sẻ, mỗi triển khai hiện độc lập nhưng có thể chia sẻ đối tượng mutex với các triển khai khác (here).

Tất cả là gì? Tôi chưa bao giờ nghe về giới hạn về số lượng mutex trong hệ thống. Hoặc bất kỳ chi phí nào liên quan đến việc tạo/hủy của họ. Mặc dù vấn đề cuối cùng có thể dễ dàng được giải quyết bằng cách tái chế các mutex thay vì phá hủy chúng.

Tôi có một trường hợp thử nghiệm đơn giản nhất để hiển thị như thế nào ấn tượng là một sự xuống cấp hiệu suất:

#include <boost/asio.hpp> 
#include <atomic> 
#include <functional> 
#include <iostream> 
#include <thread> 

std::atomic<bool> running{true}; 
std::atomic<int> counter{0}; 

struct Work 
{ 
    Work(boost::asio::io_service & io_service) 
     : _strand(io_service) 
    { } 

    static void start_the_work(boost::asio::io_service & io_service) 
    { 
     std::shared_ptr<Work> _this(new Work(io_service)); 

     _this->_strand.get_io_service().post(_this->_strand.wrap(std::bind(do_the_work, _this))); 
    } 

    static void do_the_work(std::shared_ptr<Work> _this) 
    { 
     counter.fetch_add(1, std::memory_order_relaxed); 

     if (running.load(std::memory_order_relaxed)) { 
      start_the_work(_this->_strand.get_io_service()); 
     } 
    } 

    boost::asio::strand _strand; 
}; 

struct BlockingWork 
{ 
    BlockingWork(boost::asio::io_service & io_service) 
     : _strand(io_service) 
    { } 

    static void start_the_work(boost::asio::io_service & io_service) 
    { 
     std::shared_ptr<BlockingWork> _this(new BlockingWork(io_service)); 

     _this->_strand.get_io_service().post(_this->_strand.wrap(std::bind(do_the_work, _this))); 
    } 

    static void do_the_work(std::shared_ptr<BlockingWork> _this) 
    { 
     sleep(5); 
    } 

    boost::asio::strand _strand; 
}; 


int main(int argc, char ** argv) 
{ 
    boost::asio::io_service io_service; 
    std::unique_ptr<boost::asio::io_service::work> work{new boost::asio::io_service::work(io_service)}; 

    for (std::size_t i = 0; i < 8; ++i) { 
     Work::start_the_work(io_service); 
    } 

    std::vector<std::thread> workers; 

    for (std::size_t i = 0; i < 8; ++i) { 
     workers.push_back(std::thread([&io_service] { 
      io_service.run(); 
     })); 
    } 

    if (argc > 1) { 
     std::cout << "Spawning a blocking work" << std::endl; 
     workers.push_back(std::thread([&io_service] { 
      io_service.run(); 
     })); 
     BlockingWork::start_the_work(io_service); 
    } 

    sleep(5); 
    running = false; 
    work.reset(); 

    for (auto && worker : workers) { 
     worker.join(); 
    } 

    std::cout << "Work performed:" << counter.load() << std::endl; 
    return 0; 
} 

Build nó sử dụng lệnh này:

g++ -o asio_strand_test_case -pthread -I/usr/include -std=c++11 asio_strand_test_case.cpp -lboost_system 

thử nghiệm chạy theo một cách thông thường:

time ./asio_strand_test_case 
Work performed:6905372 

real 0m5.027s 
user 0m24.688s 
sys  0m12.796s 

Chạy thử nghiệm với tác vụ chặn dài:

time ./asio_strand_test_case 1 
Spawning a blocking work 
Work performed:770 

real 0m5.031s 
user 0m0.044s 
sys  0m0.004s 

Sự khác biệt rất ấn tượng. Điều xảy ra là mỗi tác vụ không chặn mới tạo đối tượng strand mới cho đến khi nó chia sẻ cùng một triển khai với strand của tác vụ chặn. Khi điều này xảy ra, đó là một kết thúc chết, cho đến khi kết thúc công việc lâu dài.

Sửa: Giảm công việc song song xuống số đề làm việc (1000-8) và đầu ra chạy thử nghiệm cập nhật. Đã làm điều này bởi vì khi cả hai con số được gần vấn đề là nhìn thấy rõ hơn.

Trả lời

3

Vâng, một vấn đề thú vị và +1 cho chúng tôi một ví dụ nhỏ sao chép chính xác vấn đề.

Sự cố bạn đang gặp phải với việc thực hiện tăng cường là theo mặc định, chỉ có một số giới hạn strand_impl, 193 như tôi thấy trong phiên bản tăng cường (1.59).

Bây giờ, điều này có nghĩa là một số lượng lớn các yêu cầu sẽ được tranh chấp vì họ chờ khóa mở khóa bởi trình xử lý khác (sử dụng cùng một phiên bản strand_impl).

Dự đoán của tôi để thực hiện việc như vậy sẽ không cho phép quá tải hệ điều hành bằng cách tạo nhiều và rất nhiều và nhiều mutex. Điều đó sẽ rất tệ. Việc thực hiện hiện nay cho phép một để tái sử dụng các ổ khóa (và trong một cách cấu hình như chúng ta sẽ thấy dưới đây)

Trong thiết lập của tôi:

 
MacBook-Pro:asio_test amuralid$ g++ -std=c++14 -O2 -o strand_issue strand_issue.cc -lboost_system -pthread 
MacBook-Pro:asio_test amuralid$ time ./strand_issue 
Work performed:489696 

real 0m5.016s 
user 0m1.620s 
sys 0m4.069s 
MacBook-Pro:asio_test amuralid$ time ./strand_issue 1 
Spawning a blocking work 
Work performed:188480 

real 0m5.031s 
user 0m0.611s 
sys 0m1.495s 

Bây giờ, có một cách để thay đổi con số này triển khai lưu trữ bởi đặt Macro BOOST_ASIO_STRAND_IMPLEMENTATIONS.

Dưới đây là kết quả tôi nhận được sau khi cài đặt nó vào một giá trị của 1024:

 
MacBook-Pro:asio_test amuralid$ g++ -std=c++14 -DBOOST_ASIO_STRAND_IMPLEMENTATIONS=1024 -o strand_issue strand_issue.cc -lboost_system -pthread 
MacBook-Pro:asio_test amuralid$ time ./strand_issue 
Work performed:450928 

real 0m5.017s 
user 0m2.708s 
sys 0m3.902s 
MacBook-Pro:asio_test amuralid$ time ./strand_issue 1 
Spawning a blocking work 
Work performed:458603 

real 0m5.027s 
user 0m2.611s 
sys 0m3.902s 

Hầu như giống nhau cho cả hai trường hợp! Bạn có thể muốn điều chỉnh giá trị của macro theo nhu cầu của bạn để giữ độ lệch nhỏ.

+1

* "Dự đoán của tôi để làm một điều như vậy sẽ là không cho phép quá tải hệ điều hành bằng cách tạo ra rất nhiều và rất nhiều mutexes. Đó sẽ là xấu." * Tại sao? Những gì trên không có ngoài bộ nhớ số không đổi nhỏ (mỗi mutex)? –

+1

@yurikilochek Chúng là mutexes. Theo định nghĩa, chúng vô ích trừ khi được sử dụng để đồng bộ hóa. Điều đó khiến cho bộ sưu tập lớn các nguyên bản đồng bộ hóa được chờ đợi đồng thời. ':: WaitForMultipleObjectsEx' có thể không quan tâm, nhưng đó là một chuyển đổi ngữ cảnh, nó không chỉ là một vài byte bộ nhớ. Trên Linux, không có gọi AFAIK như vậy. – sehe

+0

@Arunmu Bất kể số lần triển khai vấn đề sẽ tồn tại, bởi vì nó nằm trong thiết kế. Tăng số lượng có thể giành chiến thắng một thời gian nhưng chỉ ở một mức độ nào đó. Trong ứng dụng thời gian thực, điều này sẽ không bao giờ hoạt động. Hãy thử ví dụ của tôi với 'đối tượng công việc' bằng số lượng chủ đề, tức là' 8' thay vì '1000'. Trong trường hợp đó, việc triển khai '1024' hầu như không giúp được gì (' Công việc thực hiện: 8331'). – GreenScape

2

ASIO độc lập và Boost.ASIO đã trở nên khá tách biệt trong những năm gần đây như ASIO độc lập được từ từ biến thành tham chiếu Networking TS tham chiếu để chuẩn hóa. Tất cả "hành động" đang diễn ra trong ASIO độc lập, bao gồm cả sửa lỗi chính. Chỉ có sửa lỗi rất nhỏ được thực hiện để Boost.ASIO. Có nhiều năm khác biệt giữa chúng bây giờ.

Do đó, tôi khuyên bạn nên tìm bất kỳ ai gặp vấn đề gì với Boost.ASIO nên chuyển sang ASIO độc lập. Việc chuyển đổi thường không khó, hãy xem xét nhiều cấu hình macro để chuyển đổi giữa C++ 11 và Boost trong config.hpp. Trong lịch sử Boost.ASIO thực sự được tạo tự động bởi kịch bản từ ASIO độc lập, có thể trường hợp Chris đã giữ cho các tập lệnh đó hoạt động và do đó bạn có thể tạo lại một thương hiệu Boost.ASIO mới với tất cả các thay đổi mới nhất. Tôi nghi ngờ việc xây dựng như vậy là không được kiểm tra tốt tuy nhiên.

+0

Thật thú vị @Niall Douglas. Nhìn vào các ghi chú phát hành, phiên bản cuối cùng của [standalone asio] (http://think-async.com/) để làm cho nó thành [boost] (http://www.boost.org/users/news/) là trở lại vào tháng 4 năm 2015. Phiên bản đó là 1.10.6 trong khi phát hành [phát hành asio mới nhất] (http://think-async.com/asio/asio-1.11.0/doc/asio/history.html#asio.history .asio_1_11_0) cho thấy 1.10.5 là bản phát hành chính cuối cùng, vì vậy bạn đã đúng, họ đã phân kỳ trong khi Chris đang tập trung vào Đề xuất Thư viện Mạng, bây giờ [N4612] (http://open-std.org/JTC1/ SC22/WG21/docs/papers/2016/n4612.pdf) – kenba

+0

Thật không may, chiến lược phân bổ 'strang_impl' không bị thay đổi trong phiên bản độc lập. Có một số công việc đi đúng hướng trên 'strand_executor_service'. Tôi đã cố gắng chuyển nó vào vanilla 'strand_service' nhưng không may mắn. Thiết kế hiện tại phụ thuộc quá nhiều vào việc đảm bảo rằng 'strand_impl' không bị hủy, sự kiện sau' strand' là, hầu như không thể sửa lỗi mà không cần thiết kế lại. Trong mọi trường hợp, tôi đã viết vào danh sách gửi thư. – GreenScape

Các vấn đề liên quan