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);
socket.connect(endpoint);
socket.async_receive(buffer, &handle_async_receive);
io_service.post(&print);
io_service.run();
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_receive
Operasyon (3) bilgilendirir io_service
o zaman, prizden uyumsuz okunan verilere ihtiyaç duyacağı async_receive
hemen döner.
- Gerçek işi yapmak. Bu durumda,
socket
veri 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
print
iş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_receive
işleyicisi çağrıldı ve geri döndü.
- , Üzerinden
io_service
açı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_receive
beklediği tüm verileri alamadıysa, uygulaması başka bir eşzamansız okuma işlemi gönderebilir, bu da io_service
daha fazla iş yapılmasına ve dolayısıyla geri dönülmemesine neden olabilir io_service.run()
.
O zaman not Do io_service
iş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_receive
iş ekler io_service
. Bu nedenle, io_service->run()
okuma işlemi başarıyla veya hatayla ClientReceiveEvent
tamamlanıncaya ve çalışmayı bitirene veya bir istisna atana kadar engelleyecektir .
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 =
boost::in_place(boost::ref(io_service));
boost::thread_group worker_threads;
for(int x = 0; x < 2; ++x)
{
worker_threads.create_thread(
boost::bind(&boost::asio::io_service::run, &io_service)
);
}
io_service.post(boost::bind(CalculateFib, 3));
io_service.post(boost::bind(CalculateFib, 4));
io_service.post(boost::bind(CalculateFib, 5));
work = boost::none;
worker_threads.join_all();
}
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_service
io_service::run()
io_service::work
io_service
io_service::run()
Genel akış aşağıdaki gibidir:
io_service::work
Eklenen nesneyi oluşturun ve ekleyin io_service
.
- Çağıran iş parçacığı havuzu oluşturuldu
io_service::run()
. Bu işçi iş parçacıkları nesne io_service
nedeniyle geri dönmeyecek io_service::work
.
- Fibonacci sayılarını hesaplayan 3 işleyici ekleyin
io_service
ve 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.
io_service::work
Nesneyi silin .
- Çalışan iş parçacıklarının çalışmayı bitirmesini bekleyin.
io_service
Ne 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_service
ve ardından io_service
olay 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::work
aş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));
io_service.post(boost::bind(CalculateFib, 5));
boost::thread_group worker_threads;
for(int x = 0; x < 2; ++x)
{
worker_threads.create_thread(
boost::bind(&boost::asio::io_service::run, &io_service)
);
}
worker_threads.join_all();
}
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 .