Boost :: asio :: io_service çalıştırma yöntemi blokları / engelleri kaldırırken karıştı


91

Boost.Asio'ya tamamen acemi biri olarak kafam karıştı io_service::run(). Bu yöntem engellediğinde / engeli kaldırdığında biri bana açıklayabilirse çok memnun olurum. Belgeler şöyle der:

run()Fonksiyon blokları tüm çalışma bitene kadar ve sevk edilecek artık işleyicileri vardır ya kadar io_servicedurduruldu.

Birden çok evre, işleyicileri çalıştırabilecek bir evre run()havuzu oluşturmak için işlevi çağırabilir io_service. Havuzda bekleyen tüm iş parçacıkları eşdeğerdir ve io_serviceherhangi birini bir işleyiciyi çağırmak için seçebilirler.

İşlevden normal bir çıkış run(), io_servicenesnenin durdurulduğunu gösterir ( stopped()işlev true döndürür). Sonraki aramalar için run(), run_one(), poll()veya poll_one()bir önceki çağrı olmadığı sürece hemen dönecektir reset().

Aşağıdaki ifade ne anlama geliyor?

[...] gönderilecek başka işleyici yok [...]


Davranışını anlamaya çalışırken io_service::run()bu örnekle karşılaştım (örnek 3a). İçinde io_service->run()iş emirlerinin bloke olduğunu ve beklediğini gözlemliyorum .

// WorkerThread invines io_service->run()
void WorkerThread(boost::shared_ptr<boost::asio::io_service> io_service);
void CalculateFib(size_t);

boost::shared_ptr<boost::asio::io_service> io_service(
    new boost::asio::io_service);
boost::shared_ptr<boost::asio::io_service::work> work(
   new boost::asio::io_service::work(*io_service));

// ...

boost::thread_group worker_threads;
for(int x = 0; x < 2; ++x)
{
  worker_threads.create_thread(boost::bind(&WorkerThread, io_service));
}

io_service->post( boost::bind(CalculateFib, 3));
io_service->post( boost::bind(CalculateFib, 4));
io_service->post( boost::bind(CalculateFib, 5));

work.reset();
worker_threads.join_all();

Ancak, üzerinde çalıştığım aşağıdaki kodda, istemci TCP / IP kullanarak bağlanır ve veriler eşzamansız olarak alınana kadar çalıştırma yöntemi blokları.

typedef boost::asio::ip::tcp tcp;
boost::shared_ptr<boost::asio::io_service> io_service(
    new boost::asio::io_service);
boost::shared_ptr<tcp::socket> socket(new tcp::socket(*io_service));

// Connect to 127.0.0.1:9100.
tcp::resolver resolver(*io_service);
tcp::resolver::query query("127.0.0.1", 
                           boost::lexical_cast< std::string >(9100));
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
socket->connect(endpoint_iterator->endpoint());

// Just blocks here until a message is received.
socket->async_receive(boost::asio::buffer(buf_client, 3000), 0,
                      ClientReceiveEvent);
io_service->run();

// Write response.
boost::system::error_code ignored_error;
std::cout << "Sending message \n";
boost::asio::write(*socket, boost::asio::buffer("some data"), ignored_error);

run()Aşağıdaki iki örnekte davranışını açıklayan herhangi bir açıklama takdir edilecektir.

Yanıtlar:


238

Yapı temeli

Basitleştirilmiş bir örnekle başlayalım ve ilgili Boost.Asio parçalarını inceleyelim:

void handle_async_receive(...) { ... }
void print() { ... }

...  

boost::asio::io_service io_service;
boost::asio::ip::tcp::socket socket(io_service);

...

io_service.post(&print);                             // 1
socket.connect(endpoint);                            // 2
socket.async_receive(buffer, &handle_async_receive); // 3
io_service.post(&print);                             // 4
io_service.run();                                    // 5

Bir Nedir Handler ?

Bir işleyici , bir geri aramadan başka bir şey değildir. Örnek kodda 3 işleyici vardır:

  • printİşleyicisi (1).
  • handle_async_receiveİşleyicisi (3).
  • printİşleyici (4).

Aynı print()işlev iki kez kullanılsa bile , her kullanımın kendi benzersiz şekilde tanımlanabilir işleyicisini yarattığı kabul edilir. İşleyiciler, yukarıdakiler gibi temel işlevlerden, bunlardan üretilen functors boost::bind()ve lambdas gibi daha karmaşık yapılara kadar birçok şekil ve boyutta olabilir . Karmaşıklıktan bağımsız olarak, işleyici hala bir geri aramadan başka bir şey kalmaz.

Nedir Çalışma ?

Çalışma, Boost.Asio'nun uygulama kodu adına yapması talep edilen bir işlemdir. Bazen Boost.Asio, işin bir kısmına kendisine söylendiği anda başlayabilir ve diğer zamanlarda işi daha sonraki bir zamanda yapmak için bekleyebilir. Boost.Asio, işi bitirdiğinde, sağlanan işleyiciyi çağırarak uygulamayı bilgilendirecektir .

Boost.Asio garanti işleyicileri yalnızca şu anda aradığını bir iş parçacığı içinde çalışacak run(), run_one(), poll(), veya poll_one(). Bunlar işe yarayacak ve işleyicileri arayacak iş parçacıklarıdır . Bu nedenle, yukarıdaki örnekte, (1) 'e print()gönderildiğinde çağrılmaz io_service. Bunun yerine, io_serviceöğesine eklenir ve daha sonraki bir noktada çağrılır. Bu durumda, io_service.run()(5) içinde.

Eşzamansız İşlemler Nelerdir?

Bir uyumsuz işlem çalışmalarını oluşturur ve Boost.Asio bir çağıracağı işleyici çalışması tamamlandığında uygulamasını bilgilendirmek için. Eşzamansız işlemler, önekli bir ada sahip bir işlevin çağrılmasıyla oluşturulur async_. Bu işlevler aynı zamanda başlatma işlevleri olarak da bilinir .

Eşzamansız işlemler üç benzersiz adıma ayrılabilir:

  • io_serviceİşlerin yapılması gereken ilgili kişiyi başlatmak veya bilgilendirmek . async_receiveOperasyon (3) bilgilendirir io_serviceo zaman, prizden uyumsuz okunan verilere ihtiyaç duyacağı async_receivehemen döner.
  • Gerçek işi yapmak. Bu durumda, socketveri alındığında , baytlar okunur ve içine kopyalanır buffer. Asıl çalışma şunlardan birinde yapılacaktır:
    • Başlatma işlevi (3), eğer Boost.Asio engellemeyeceğini belirleyebilirse.
    • Uygulama açıkça io_service(5).
  • handle_async_receive ReadHandler'ı çağırma . Bir kez daha, işleyiciler yalnızca io_service. Böylece, işin ne zaman yapıldığına bakılmaksızın (3 veya 5), handle_async_receive()sadece io_service.run()(5) içinde çağrılacağı garanti edilir .

Bu üç adım arasındaki zaman ve boşluk ayrımı, kontrol akışının ters çevrilmesi olarak bilinir. Eşzamansız programlamayı zorlaştıran karmaşıklıklardan biridir. Ancak, koroutinler kullanmak gibi bunu azaltmaya yardımcı olabilecek teknikler vardır .

Ne Yapar io_service.run()?

Bir iş parçacığı çağırdığında io_service.run(), iş ve işleyiciler bu iş parçacığı içinden çağrılacaktır. Yukarıdaki örnekte, io_service.run()(5) şunlardan birine kadar engelleyecektir:

  • Her iki printişleyiciden de çağırdı ve geri döndü , alma işlemi başarılı veya başarısız bir şekilde tamamlandı ve handle_async_receiveişleyicisi çağrıldı ve geri döndü.
  • , Üzerinden io_serviceaçıkça durduruldu io_service::stop().
  • Bir işleyicinin içinden bir istisna atılır.

Potansiyel bir psuedo-ish akışı şu şekilde tanımlanabilir:

io_service oluştur
soket oluştur
io_service (1) 'e yazdırma işleyicisi ekle
soketin bağlanmasını bekleyin (2)
io_service (3) 'e bir eşzamansız okuma çalışması isteği ekleyin
io_service (4) 'e yazdırma işleyicisi ekle
io_service'i çalıştır (5)
  iş veya işleyiciler var mı?
    evet, 1 iş ve 2 işleyici var
      sokette veri var mı? hayır hiçbir şey yapma
      yazdırma işleyicisini çalıştır (1)
  iş veya işleyiciler var mı?
    evet, 1 iş ve 1 işleyici var
      sokette veri var mı? hayır hiçbir şey yapma
      yazdırma işleyicisini çalıştır (4)
  iş veya işleyiciler var mı?
    evet, 1 iş var
      sokette veri var mı? hayır, beklemeye devam et
  - soket verileri alır -
      soket veriye sahip, arabelleğe oku
      io_service'e handle_async_receive işleyicisi ekleyin
  iş veya işleyiciler var mı?
    evet, 1 işleyici var
      handle_async_receive işleyicisini çalıştır (3)
  iş veya işleyiciler var mı?
    hayır, io_service'i durdurulmuş olarak ayarla ve geri dön

Okuma bittiğinde Bildirimi nasıl, başka ilave işleyicisi için io_service. Bu ince ayrıntı, eşzamansız programlamanın önemli bir özelliğidir. Çalışabilmesine imkan işleyicileri birlikte zincirleme edilecek. Örneğin, handle_async_receivebeklediği tüm verileri alamadıysa, uygulaması başka bir eşzamansız okuma işlemi gönderebilir, bu da io_servicedaha fazla iş yapılmasına ve dolayısıyla geri dönülmemesine neden olabilir io_service.run().

O zaman not Do io_serviceişsiz ran vardır, uygulama gerekir çalıştırmadan önce tekrar.reset()io_service


Örnek Soru ve Örnek 3a kodu

Şimdi soruda atıfta bulunulan iki kod parçasını inceleyelim.

Soru Kodu

socket->async_receiveiş ekler io_service. Bu nedenle, io_service->run()okuma işlemi başarıyla veya hatayla ClientReceiveEventtamamlanıncaya ve çalışmayı bitirene veya bir istisna atana kadar engelleyecektir .

Örnek 3a Kod

Anlaşılmasını kolaylaştırmak umuduyla, burada daha küçük açıklamalı bir Örnek 3a verilmiştir:

void CalculateFib(std::size_t n);

int main()
{
  boost::asio::io_service io_service;
  boost::optional<boost::asio::io_service::work> work =       // '. 1
      boost::in_place(boost::ref(io_service));                // .'

  boost::thread_group worker_threads;                         // -.
  for(int x = 0; x < 2; ++x)                                  //   :
  {                                                           //   '.
    worker_threads.create_thread(                             //     :- 2
      boost::bind(&boost::asio::io_service::run, &io_service) //   .'
    );                                                        //   :
  }                                                           // -'

  io_service.post(boost::bind(CalculateFib, 3));              // '.
  io_service.post(boost::bind(CalculateFib, 4));              //   :- 3
  io_service.post(boost::bind(CalculateFib, 5));              // .'

  work = boost::none;                                         // 4
  worker_threads.join_all();                                  // 5
}

Yüksek düzeyde, program io_service'ın olay döngüsünü (2) işleyecek 2 iş parçacığı oluşturacaktır . Bu, Fibonacci sayılarını (3) hesaplayacak basit bir iş parçacığı havuzuyla sonuçlanır.

Soru Kodu ile bu kod arasındaki tek büyük fark, bu kodun fiili çalışma ve işleyiciler (3) 'e eklenmeden önceio_service::run() (2)' yi çağırmasıdır . Hemen geri dönmesini önlemek için bir nesne oluşturulur (1). Bu nesne , işin bitmesini engeller ; bu nedenle hiçbir çalışma sonucu geri dönmeyecektir.io_serviceio_service::run()io_service::workio_serviceio_service::run()

Genel akış aşağıdaki gibidir:

  1. io_service::workEklenen nesneyi oluşturun ve ekleyin io_service.
  2. Çağıran iş parçacığı havuzu oluşturuldu io_service::run(). Bu işçi iş parçacıkları nesne io_servicenedeniyle geri dönmeyecek io_service::work.
  3. Fibonacci sayılarını hesaplayan 3 işleyici ekleyin io_serviceve hemen geri dönün. Ana iş parçacığı değil, çalışan iş parçacıkları bu işleyicileri hemen çalıştırmaya başlayabilir.
  4. io_service::workNesneyi silin .
  5. Çalışan iş parçacıklarının çalışmayı bitirmesini bekleyin. io_serviceNe işleyicileri ne de işleyicileri olmadığından, bu sadece 3 işleyicinin tümü yürütmeyi bitirdiğinde gerçekleşir .

Kod, işleyicilerin eklendiği io_serviceve ardından io_serviceolay döngüsünün işlendiği Orijinal Kod ile aynı şekilde farklı şekilde yazılabilir . Bu, kullanma ihtiyacını ortadan kaldırır ve io_service::workaşağıdaki kodla sonuçlanır:

int main()
{
  boost::asio::io_service io_service;

  io_service.post(boost::bind(CalculateFib, 3));              // '.
  io_service.post(boost::bind(CalculateFib, 4));              //   :- 3
  io_service.post(boost::bind(CalculateFib, 5));              // .'

  boost::thread_group worker_threads;                         // -.
  for(int x = 0; x < 2; ++x)                                  //   :
  {                                                           //   '.
    worker_threads.create_thread(                             //     :- 2
      boost::bind(&boost::asio::io_service::run, &io_service) //   .'
    );                                                        //   :
  }                                                           // -'
  worker_threads.join_all();                                  // 5
}

Eşzamanlı ve Eşzamansız

Söz konusu kod eşzamansız bir işlem kullanıyor olsa da, eşzamansız işlemin tamamlanmasını beklediği için eşzamanlı olarak etkili bir şekilde çalışıyor:

socket.async_receive(buffer, handler)
io_service.run();

eşdeğerdir:

boost::asio::error_code error;
std::size_t bytes_transferred = socket.receive(buffer, 0, error);
handler(error, bytes_transferred);

Genel bir kural olarak, senkronize ve asenkron işlemleri karıştırmaktan kaçınmaya çalışın. Çoğu zaman karmaşık bir sistemi karmaşık bir sisteme dönüştürebilir. Bu cevap , bazıları Boost.Asio belgelerinde de ele alınan eşzamansız programlamanın avantajlarını vurgulamaktadır .


13
Harika gönderi. Sadece bir şey eklemek istiyorum çünkü yeterince dikkat çekmediğini hissediyorum: run () geri döndükten sonra, onu tekrar çalıştırmadan önce io_service'inizde reset () çağırmanız gerekir. Aksi takdirde bekleyen async_ işlemler olsun ya da olmasın anında geri dönebilir.
DeVadder

Tampon nereden geliyor? Bu ne?
ruip Pacheco

Hala kafam karışık. Karıştırma eşzamanlıysa ve eşzamansız önerilmiyorsa, saf eşzamansız mod nedir? io_service.run () olmadan kodu gösteren bir örnek verebilir misiniz?
Splash

@Splash One, io_service.poll()olağanüstü işlemleri engellemeden olay döngüsünü işlemek için kullanılabilir . Eşzamanlı ve eşzamansız işlemleri karıştırmaktan kaçınmak için birincil öneri, gereksiz karmaşıklık eklemekten kaçınmak ve işleyicilerin tamamlanması uzun zaman aldığında zayıf yanıt vermeyi önlemektir. Senkron işlemin engellenmeyeceğini bildiğinde olduğu gibi güvenli olduğu bazı durumlar vardır.
Tanner Sansbury

"Boost.Asio, işleyicilerin yalnızca şu andarun() .... çağıran bir iş parçacığı içinde çalışacağını garanti eder " içindeki "şu anda" ile ne demek istiyorsun ? N iş parçacığı varsa (hangisi çağırdı run()), hangisi "geçerli" iş parçacığı? Çok olabilir mi? Veya async_*()(diyelim async_read) yürütmeyi bitiren iş parçacığının işleyicilerini de çağırması garantili mi?
Nawaz

19

Neyin nasıl yapıldığını basitleştirmek runiçin, bir kağıt yığınını işlemesi gereken bir çalışan olarak düşünün; bir sayfa alır, yaprağın söylediğini yapar, kağıdı atar ve bir sonrakini alır; çarşafları bittiğinde ofisten çıkar. Her kağıda, yığına yeni bir sayfa eklemek bile her tür talimat olabilir. Geri asio için: Bir verebileceğiniz io_serviceesasen iki şekilde çalışması: kullanarak postsen bağlantılı numunede olarak üzerine veya dahili olarak çağırır diğer nesneleri kullanarak postilgili io_servicegibi socketve async_*yöntemlerle.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.