Skip to content

Latest commit

 

History

History
942 lines (630 loc) · 32.3 KB

File metadata and controls

942 lines (630 loc) · 32.3 KB

四、Boost.Asio 入门

我们已经大致了解了 Boost C++ 库。现在是时候了解更多关于 Boost 的信息了.Asio,我们用来开发网络应用的库。助推.Asio 是用来异步处理数据的库的集合,因为 Asio 本身代表异步 I/O ( 输入输出)。异步意味着程序中的特定任务将在不阻塞其他任务和 Boost 的情况下运行.Asio 将在完成任务后通知程序。换句话说,任务是并发执行的。

在本章中,我们将讨论以下主题:

  • 区分并发和非并发编程
  • 了解输入/输出服务、Boost 的大脑和心脏.Asio
  • 将函数动态绑定到函数指针
  • 同步访问任何全局数据或共享数据

越来越接近助推.Asio 库

假设我们正在开发一个音频下载器应用,我们希望用户能够导航到应用中的所有菜单,即使下载过程正在进行中。如果我们不使用异步编程,应用将被下载过程阻止,用户将不得不等到文件下载完成。但是由于异步编程,用户不需要等到下载过程完成才能继续使用应用。

换句话说,一个同步的过程就像是在剧场售票线上排队。只有当我们到达售票柜台时,我们才会得到服务,在此之前,我们必须等待排在我们前面的前顾客的所有流程完成。相比之下,我们可以想象异步过程就像在餐厅用餐,服务员不必等待顾客的订单由厨师准备。服务员可以去接其他顾客的订单,而不是耽误时间等厨师。

Boost库也有用来并发执行任务的Boost.Thread库,但是Boost.Thread库是用来访问内部资源的,比如 CPU 核心资源,而Boost.Asio库是用来访问外部资源的,比如网络连接,因为数据是通过网卡收发的。

让我们区分和并发和非并发编程。请看下面的代码:

/* nonconcurrent.cpp */
#include <iostream>

void Print1(void) {
  for(int i=0; i<5; i++) {
    std::cout << "[Print1] Line: " << i << "\n";
  }
}

void Print2(void) {
  for(int i=0; i<5; i++) {
    std::cout << "[Print2] Line: " << i << "\n";
  }
}

int main(void) {
  Print1();
  Print2();
  return 0;
}

前面的代码是非电流程序。将代码保存为nonconcurrent.cpp,然后使用以下命令进行编译:

g++ -Wall -ansi nonconcurrent.cpp -o nonconcurrent

运行nonconcurrent.cpp后,会有这样的输出显示在你面前:

Getting closer to the Boost.Asio library

我们要运行两个功能:Print1()Print2()。在非电流编程中,应用首先运行Print1()功能,然后完成功能中的所有指令。程序继续调用Print2()功能,直到指令完全运行。

现在,让我们比较非并发编程和并发编程。为此,请看下面的代码:

/* concurrent.cpp */
#include <boost/thread.hpp>
#include <boost/chrono.hpp>
#include <iostream>

void Print1() {
  for (int i=0; i<5; i++) {
    boost::this_thread::sleep_for(boost::chrono::milliseconds{500});
    std::cout << "[Print1] Line: " << i << '\n';
  }
}

void Print2() {
  for (int i=0; i<5; i++) {
    boost::this_thread::sleep_for(boost::chrono::milliseconds{500});
    std::cout << "[Print2] Line: " << i << '\n';
  }
}

int main(void) {
  boost::thread_group threads;
  threads.create_thread(Print1);
  threads.create_thread(Print2);
  threads.join_all();
}

将前面的代码保存为concurrent.cpp,并使用以下命令进行编译:

g++ -ansi -std=c++ 11 -I ../boost_1_58_0 concurrent.cpp -o concurrent -L ../boost_1_58_0/stage/lib -lboost_system-mgw49-mt-1_58 -lws2_32 -l boost_thread-mgw49-mt-1_58 -l boost_chrono-mgw49-mt-1_58

运行程序,得到如下输出:

Getting closer to the Boost.Asio library

从前面的输出中我们可以看到Print1()Print2()功能是并发运行的。Print2()函数不需要等待Print1()函数完成所有要调用的指令的执行。这就是为什么我们称之为并发编程。

类型

如果在代码中包含库,请不要忘记复制关联的动态库文件。例如,如果您使用–l选项包含boost_system-mgw49-mt-1_58,您必须复制libboost_system-mgw49-mt-1_58.dll文件并将其粘贴到与输出可执行文件相同的目录中。

检查提升中的输入/输出服务.Asio 库

Boost::Asio命名空间的核心对象是io_service输入/输出服务是一个通道,用于访问操作系统资源,并在我们的程序和执行输入/输出请求的操作系统之间建立通信。还有一个 I/O 对象有提交 I/O 请求的作用。对于实例,tcp::socket对象将从我们的程序向操作系统提供套接字编程请求。

使用和阻止 run()功能

输入/输出服务对象中最常使用的功能之一是run()功能。用于运行io_service对象的事件处理循环。它将阻塞下一个语句程序,直到io_service对象中的所有工作完成,并且不再有要调度的处理程序。如果我们停止io_service对象,它将不再阻塞程序。

在编程中,event是程序检测到的动作或事件,将由程序使用event handler对象处理。io_service对象有一个或多个处理事件的实例,即event processing loop

现在,让我们看看下面的代码片段:

/* unblocked.cpp */
#include <boost/asio.hpp>
#include <iostream>

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

  io_svc.run();

  std::cout << "We will see this line in console window." << std::endl;

  return 0;
}

我们将前面的代码保存为unblocked.cpp,然后运行以下命令进行编译:

g++ -Wall -ansi -I ../boost_1_58_0 unblocked.cpp -o unblocked -L ../boost_1_58_0/stage/lib -l boost_system-mgw49-mt-1_58 -l ws2_32

当我们运行程序时,会显示以下输出:

We will see this line in console window.

然而,为什么我们仍然在控制台中获得文本行,尽管以前我们知道run()函数在被调用后会阻止下一个函数?这是因为我们没有给io_service对象任何工作。既然io_service没有工作要做,io_service对象就不应该屏蔽程序。

现在,让我们给io_service对象一些工作要做。这方面的程序如下代码所示:

/* blocked.cpp */
#include <boost/asio.hpp>
#include <iostream>

int main(void) {
  boost::asio::io_service io_svc;
  boost::asio::io_service::work worker(io_svc);

  io_svc.run();

  std::cout << "We will not see this line in console window :(" << std::endl;

  return 0;
}

将前面的代码命名为blocked.cpp和,然后在我们的控制台窗口中键入以下命令进行编译:

g++ -Wall -ansi -I ../boost_1_58_0 blocked.cpp -o blocked -L ../boost_1_58_0/stage/lib -l boost_system-mgw49-mt-1_58 -l ws2_32

如果我们通过在控制台中键入blocked来运行程序,我们将不再看到文本行,因为我们添加了以下代码行:

boost::asio::io_service::work work(io_svc);

work类负责告诉io_service对象什么时候开始工作,什么时候结束工作。它将确保在工作进行期间io_service对象中的run()功能不会退出。此外,当没有未完成的工作时,它将确保run()功能确实退出。在我们前面的代码中,work类通知io_service对象它有工作要做,但是我们没有定义工作是什么。因此,程序将被无限阻塞,并且不会显示输出。之所以被屏蔽,是因为run()功能被调用,即使我们仍然可以通过按 Ctrl + C 来终止程序。

使用非阻塞轮询()功能

现在,我们暂时离开run()功能,尝试使用poll()功能。poll()功能用于运行准备好的处理程序,直到没有剩余的准备好的处理程序,或者直到io_service对象已经停止。但是,与run()功能相比,poll()功能不会阻塞程序。

让我们键入以下使用poll()功能的代码,并将其保存为poll.cpp:

/* poll.cpp */
#include <boost/asio.hpp>
#include <iostream>

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

  for(int i=0; i<5; i++) {
    io_svc.poll();
    std::cout << "Line: " << i << std::endl;
  }

  return 0;
}

然后,使用以下命令编译poll.cpp:

g++ -Wall -ansi -I ../boost_1_58_0 poll.cpp -o poll -L ../boost_1_58_0/stage/lib -l boost_system-mgw49-mt-1_58 -l ws2_32

因为没有io_service对象要做的工作,程序应该如下显示五行文本:

Using the non-blocking poll() function

然而,如果我们在使用poll()功能时给io_service对象做功呢?为了找到答案,让我们输入以下代码并将其保存为pollwork.cpp:

/* pollwork.cpp */
#include <boost/asio.hpp>
#include <iostream>

int main(void) {
  boost::asio::io_service io_svc;
  boost::asio::io_service::work work(io_svc);

  for(int i=0; i<5; i++) {
    io_svc.poll();
    std::cout << "Line: " << i << std::endl;
  }

  return 0;
}

要编译pollwork.cpp,使用以下命令:

g++ -Wall -ansi -I ../boost_1_58_0 pollwork.cpp -o pollwork -L ../boost_1_58_0/stage/lib -l boost_system-mgw49-mt-1_58 -l ws2_32

poll.cpp文件和pollwork.cpp文件的区别只有以下几行:

boost::asio::io_service::work work(io_svc);

但是,如果我们运行pollwork.exe,我们将获得与poll.exe相同的输出。这是因为,如我们之前所知,poll()功能不会在有更多工作要做时阻止程序。它将执行当前工作,然后返回值。

移除工作对象

我们也可以通过从io_service对象中移除work对象来解锁程序,但是我们必须在中使用指向work对象的指针来移除work对象本身。我们将使用shared_ptr指针,一个由Boost库提供的智能指针。

让我们使用blocked.cpp的修改代码。这方面的代码如下:

/* removework.cpp */
#include <boost/asio.hpp>
#include <boost/shared_ptr.hpp>
#include <iostream>

int main(void) {
  boost::asio::io_service io_svc;
  boost::shared_ptr<boost::asio::io_service::work> worker(
    new boost::asio::io_service::work(io_svc)
  );

  worker.reset();

  io_svc.run();

  std::cout << "We will not see this line in console window :(" << std::endl;

  return 0;
}

将前面的代码保存为removework.cpp,并使用以下命令编译它:

g++ -Wall -ansi -I ../boost_1_58_0 removework.cpp -o removework -L ../boost_1_58_0/stage/lib -l boost_system-mgw49-mt-1_58 -l ws2_32

当我们运行removework.cpp的时候,相比于blocked.cpp会无限的阻塞程序,下面一行文字会显示给我们:

Removing the work object

现在,让我们剖析一下代码。正如我们在前面的代码中看到的,我们使用了shared_ptr指针来实例化work对象。借助 Boost 提供的智能指针,我们不再需要手动删除内存分配来存储指针,因为它保证了当最后一个指针被破坏或重置时,所指向的对象将被删除。不要忘记在boost目录中包含shared_ptr.hpp,因为shared_ptr指针是在头文件中定义的。

我们还添加了reset()函数,以便在准备后续的run()函数调用时重置io_service对象。在调用run()poll()函数之前,必须调用reset()函数。它还会告诉shared_ptr指针自动销毁我们创建的指针。关于share_ptr指针的更多信息,可在www.boost.org/doc/libs/1_58_0/libs/smart_ptr/shared_ptr.htm找到。

前面的程序解释了我们已经成功地从io_service对象中移除了对象。我们可以使用这个功能,如果我们打算完成所有悬而未决的工作,即使它实际上还没有完成。

处理多个线程

到目前为止,我们只为一个io_service对象处理了一个线程。如果我们想在单个io_service对象中处理更多的线程,下面的代码将解释如何做到这一点:

/* multithreads.cpp */
#include <boost/asio.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/thread.hpp>
#include <iostream>

boost::asio::io_service io_svc;
int a = 0;

void WorkerThread() {
  std::cout << ++ a << ".\n";
  io_svc.run();
  std::cout << "End.\n";
}

int main(void) {
  boost::shared_ptr<boost::asio::io_service::work> worker(
    new boost::asio::io_service::work(io_svc)
  );

  std::cout << "Press ENTER key to exit!" << std::endl;

  boost::thread_group threads;
  for(int i=0; i<5; i++)
    threads.create_thread(WorkerThread);

  std::cin.get();

  io_svc.stop();

  threads.join_all();

  return 0;
}

给前面的代码命名为mutithreads.cpp,然后使用以下命令编译它:

g++ -Wall -ansi -I ../boost_1_58_0 multithreads.cpp -o multithreads -L ../boost_1_58_0/stage/lib -l boost_system-mgw49-mt-1_58 -l ws2_32 -l boost_thread-mgw49-mt-1_58

我们将包含在thread.hpp头文件中,这样我们就可以使用头文件中定义的对象。线程本身是一段可以独立运行的指令序列,所以我们可以同时运行多个线程。

现在,在我们的控制台中运行mutithreads.exe。我通过运行它获得了以下输出:

Dealing with many threads

您可能会获得不同的输出,因为被设置为线程池的所有线程都是彼此等效的。io_service对象可以随机选择其中任意一个并调用其处理程序,所以我们不能保证io_service对象是否会顺序选择一个线程:

for(int i=0; i<5; i++)
 threads.create_thread(WorkerThread);

使用前面的代码片段,我们可以创建五个线程来显示文本行,如您在前面的截图中所见。对于本例来说,五行文本足以查看非电流的顺序:

std::cout << ++ a << ".\n";
io_svc.run();

在创建的每个线程中,程序将调用run()函数来运行io_service对象的工作。调用一次run()函数是不够的,因为所有非工作人员都将在run()对象完成所有工作后被调用。

创建五个线程后,程序运行io_service对象的工作:

std::cin.get();

所有工作运行完毕后,程序等待您使用前面的代码片段从键盘上按下进入键:

io_svc.stop();

一旦用户按下进入键,程序将点击前面的代码片段。stop()功能会通知io_service对象所有工作都应该停止。这意味着程序将停止我们拥有的五个线程:

threads.join_all();

join_all()功能随后将继续处理所有未完成的线程,程序将等待,直到所有线程中的所有进程都完成。前面的代码片段将在WorkerThread()块中继续下面的语句:

std::cout << "End.\n";

所以在我们按下进入键后,程序将完成它剩余的代码,我们将获得如下剩余的输出:

Dealing with many threads

了解助力。绑定库

我们已经能够使用io_service对象并初始化work对象。在这之后我们应该知道的是如何给io_service对象一些工作。但是在我们向io_service服务提供工作之前,我们需要了解boost::bind库。

Boost.Bind库用于简化函数指针的调用。它将语法从深奥和令人困惑的东西转换成易于理解的东西。

包装函数调用

让我们按照的顺序查看下面的代码,以了解如何包装函数调用:

/* uncalledbind.cpp */
#include <boost/bind.hpp>
#include <iostream>

void func() {
  std::cout << "Binding Function" << std::endl;
}

int main(void) {
  boost::bind(&func);
  return 0;
}

将前面的代码保存为uncalledbind.cpp,然后使用以下命令进行编译:

g++ -Wall -ansi -I ../boost_1_58_0 uncalledbind.cpp -o uncalledbind

我们不会得到任何一行文本作为输出,因为我们只是创建了一个函数调用,但实际上并没有调用它。我们必须将其添加到()运算符中,以调用如下函数:

/* calledbind.cpp */
#include <boost/bind.hpp>
#include <iostream>

void func() {
  std::cout << "Binding Function" << std::endl;
}

int main(void) {
  boost::bind(&func)();
  return 0;
}

命名前面的代码calledbind.cpp并运行以下命令编译它:

g++ -Wall -ansi -I ../boost_1_58_0 calledbind.cpp -o calledbind

现在,如果我们运行程序,我们将获得文本行作为输出,当然,我们将看到bind()函数作为输出:

boost::bind(&func)();

正如我们在整个代码中所看到的,更改只发生在一行中,如前面的代码片段所示。

现在,让我们使用带有参数的函数来传递。我们将在下面的代码中为此目的使用boost::bind:

/* argumentbind.cpp */
#include <boost/bind.hpp>
#include <iostream>

void cubevolume(float f) {
  std::cout << "Volume of the cube is " << f * f * f << std::endl;
}

int main(void) {
  boost::bind(&cubevolume, 4.23f)();
  return 0;
}

运行以下命令编译前面的argumentbind.cpp文件:

g++ -Wall -ansi -I ../boost_1_58_0 argumentbind.cpp -o argumentbind

我们使用boost::bind成功调用了带有参数的函数,因此我们获得了以下输出:

Volume of the cube is 75.687

您需要记住,如果函数有多个参数,我们必须精确匹配函数签名。下面的代码将对此进行更详细的解释:

/* signaturebind.cpp */
#include <boost/bind.hpp>
#include <iostream>
#include <string>

void identity(std::string name, int age, float height) {
  std::cout << "Name   : " << name << std::endl;
  std::cout << "Age    : " << age << " years old" << std::endl;
  std::cout << "Height : " << height << " inch" << std::endl;
}

int main(int argc, char * argv[]) {
  boost::bind(&identity, "John", 25, 68.89f)();
  return 0;
}

使用以下命令编译signaturebind.cpp代码:

g++ -Wall -ansi -I ../boost_1_58_0 signaturebind.cpp -o signaturebind

身份函数的签名是std::stringintfloat。因此,我们必须分别用std::stringintfloat填充bind参数。

因为我们已经精确地匹配了函数签名,我们将获得如下输出:

Wrapping a function invocation

我们已经能够在boost::bind中调用global()函数。现在,让我们继续在boost::bind中调用类内的函数。这方面的代码如下:

/* classbind.cpp */
#include <boost/bind.hpp>
#include <iostream>
#include <string>

class TheClass {
public:
  void identity(std::string name, int age, float height) {
    std::cout << "Name   : " << name << std::endl;
    std::cout << "Age    : " << age << " years old" << std::endl;
    std::cout << "Height : " << height << " inch" << std::endl;
  }
};

int main(void) {
  TheClass cls;
  boost::bind(&TheClass::identity, &cls, "John", 25, 68.89f)();
  return 0;
}

使用以下命令编译前面的classbind.cpp代码:

g++ -Wall -ansi -I ../boost_1_58_0 classbind.cpp -o classbind

这个输出将与signaturebind.cpp代码完全相同,因为函数的内容也完全相同:

boost::bind(&TheClass::identity, &cls, "John", 25, 68.89f)();

正如我们在前面的代码片段中看到的,我们必须传递带有类和函数名的boost:bind参数、类的对象和基于函数签名的参数。

使用助推器。绑定库

到目前为止,我们已经能够将boost::bind用于全局和类函数。但是,当将io_service对象与boost::bind一起使用时,我们会出现不可复制的错误,因为io_service对象无法复制。

现在,我们再来看看multithreads.cpp。我们将修改代码来解释io_service对象的boost::bind的用法,我们仍然需要shared_ptr指针的帮助。让我们看看下面的代码片段:

/* ioservicebind.cpp */
#include <boost/asio.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <iostream>

void WorkerThread(boost::shared_ptr<boost::asio::io_service> iosvc, int counter) {
  std::cout << counter << ".\n";
  iosvc->run();
  std::cout << "End.\n";
}

int main(void) {
  boost::shared_ptr<boost::asio::io_service> io_svc(
    new boost::asio::io_service
  );

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

  std::cout << "Press ENTER key to exit!" << std::endl;

  boost::thread_group threads;
  for(int i=1; i<=5; i++)
    threads.create_thread(boost::bind(&WorkerThread, io_svc, i));

  std::cin.get();

  io_svc->stop();

  threads.join_all();

  return 0;
}

我们将前面的代码命名为ioservicebind.cpp,并使用以下命令进行编译:

g++ -Wall -ansi -I ../boost_1_58_0 ioservicebind.cpp -o ioservicebind –L ../boost_1_58_0/stage/lib -l boost_system-mgw49-mt-1_58 -l ws2_32 -l boost_thread-mgw49-mt-1_58

当我们运行ioservicebind.exe时,我们获得与multithreads.exe相同的输出,但当然,程序会随机化所有线程的顺序:

boost::shared_ptr<boost::asio::io_service> io_svc(
 new boost::asio::io_service
);

我们实例化shared_ptr指针中的io_service对象,使其可复制,这样我们就可以将其绑定到我们用作线程处理程序的工人thread()函数:

void WorkerThread(boost::shared_ptr<boost::asio::io_service> iosvc, int counter)

前面的代码片段向我们展示了io_service对象可以被传递给函数。我们不需要像在multithreads.cpp代码片段中那样定义int全局变量,因为我们也可以将int参数传递给WorkerThread()函数:

std::cout << counter << ".\n";

现在,不是增加显示给用户的int变量。我们可以使用前面的代码片段,因为我们从main块中的for循环传递了计数器。

如果我们看一下create_thread()函数,我们会看到它在ioservicebind.cppmultithreads.cpp文件中得到不同的参数。我们可以传递一个指向void()函数的指针,该函数不接受任何参数作为create_thread()函数的参数,如我们在multithreads.cpp文件中所见。我们还可以将绑定函数作为参数传递给create_thread()函数,正如我们在ioservicebind.cpp文件中看到的那样。

使数据访问与升压同步。互斥库

你运行multithreads.exe或者ioservicebind.exe可执行文件的时候有没有得到过下面的输出?

Synchronizing data access with the Boost.Mutex library

我们可以在前面的截图中看到这里有一个格式问题。因为std::cout对象是一个全局对象,同时从不同的线程写入它可能会导致输出格式问题。为了解决这个问题,我们可以使用一个mutex对象,该对象可以在thread库提供的boost::mutex对象中找到。互斥体用于同步对任何全局数据或共享数据的访问。为了更好地理解 Mutex,请看下面的代码:

/* mutexbind.cpp */
#include <boost/asio.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <iostream>

boost::mutex global_stream_lock;

void WorkerThread(boost::shared_ptr<boost::asio::io_service> iosvc, int counter) {
  global_stream_lock.lock();
  std::cout << counter << ".\n";
  global_stream_lock.unlock();

  iosvc->run();

  global_stream_lock.lock();
  std::cout << "End.\n";
  global_stream_lock.unlock();
}

int main(void) {
  boost::shared_ptr<boost::asio::io_service> io_svc(
    new boost::asio::io_service
  );

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

  std::cout << "Press ENTER key to exit!" << std::endl;

  boost::thread_group threads;
  for(int i=1; i<=5; i++)
    threads.create_thread(boost::bind(&WorkerThread, io_svc, i));

  std::cin.get();

  io_svc->stop();

  threads.join_all();

  return 0;
}

将前面的代码保存为mutexbind.cpp和,然后使用以下命令进行编译:

g++ -Wall -ansi -I ../boost_1_58_0 mutexbind.cpp -o mutexbind -L ../boost_1_58_0/stage/lib -l boost_system-mgw49-mt-1_58 -l ws2_32 -l boost_thread-mgw49-mt-1_58

现在,运行mutexbind.cpp文件,我们将不再面临格式问题:

boost::mutex global_stream_lock;

我们实例化新的mutex对象,global_stream_lock。有了这个对象,我们可以调用lock()unlock()函数。lock()函数将阻止访问相同函数的其他线程等待当前线程完成。如果只有当前线程调用了unlock()函数,其他线程可以访问相同的函数。需要记住的一点是,我们不应该递归调用lock()函数,因为如果lock()函数没有被unlock()函数解锁,那么就会出现线程死锁,从而冻结应用。所以,我们在使用lock()unlock()功能时一定要小心。

给输入输出服务一些工作

现在,是时候给io_service对象一些工作了。更多的了解boost::bindboost::mutex会帮助我们给io_service对象工作做。io_service对象中有两个成员函数:post()dispatch()函数,我们将经常使用这两个函数。post()功能是在我们将所有工作排队后,请求io_service对象运行io_service对象的工作,所以不允许我们立即运行工作。而dispatch()功能也是用来向io_service对象发出请求运行io_service对象的工作,但它会马上执行工作而不排队。

使用 post()函数

让我们通过创建下面的代码来检查post()函数。我们将使用mutexbind.cpp文件作为我们的基础代码,因为我们将只修改源代码:

/* post.cpp */
#include <boost/asio.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <iostream>

boost::mutex global_stream_lock;

void WorkerThread(boost::shared_ptr<boost::asio::io_service> iosvc, int counter) {
  global_stream_lock.lock();
  std::cout << counter << ".\n";
  global_stream_lock.unlock();

  iosvc->run();

  global_stream_lock.lock();
  std::cout << "End.\n";
  global_stream_lock.unlock();
}

size_t fac(size_t n) {
  if ( n <= 1 ) {
    return n;
  }
  boost::this_thread::sleep(
    boost::posix_time::milliseconds(1000)
  );
  return n * fac(n - 1);
}

void CalculateFactorial(size_t n) {
  global_stream_lock.lock();
  std::cout << "Calculating " << n << "! factorial" << std::endl;
  global_stream_lock.unlock();

  size_t f = fac(n);

  global_stream_lock.lock();
  std::cout << n << "! = " << f << std::endl;
  global_stream_lock.unlock();
}

int main(void) {
  boost::shared_ptr<boost::asio::io_service> io_svc(
    new boost::asio::io_service
  );

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

  global_stream_lock.lock();
  std::cout << "The program will exit once all work has finished." << std::endl;
  global_stream_lock.unlock();

  boost::thread_group threads;
  for(int i=1; i<=5; i++)
    threads.create_thread(boost::bind(&WorkerThread, io_svc, i));

  io_svc->post(boost::bind(CalculateFactorial, 5));
  io_svc->post(boost::bind(CalculateFactorial, 6));
  io_svc->post(boost::bind(CalculateFactorial, 7));

  worker.reset();

  threads.join_all();

  return 0;
}

将前面的代码命名为post.cpp,并使用下面的命令进行编译:

g++ -Wall -ansi -I ../boost_1_58_0 post.cpp -o post -L ../boost_1_58_0/stage/lib -l boost_system-mgw49-mt-1_58 -l ws2_32 -l boost_thread-mgw49-mt-1_58

在运行程序之前,让我们检查代码以了解其行为:

size_t fac(size_t n) {
 if (n <= 1) {
 return n;
 }
 boost::this_thread::sleep(
 boost::posix_time::milliseconds(1000)
 );
 return n * fac(n - 1);
}

我们添加fac()函数来递归计算 n 阶乘。为了查看工作线程的工作情况,会有一个时间延迟来减慢进程:

io_svc->post(boost::bind(CalculateFactorial, 5));
io_svc->post(boost::bind(CalculateFactorial, 6));
io_svc->post(boost::bind(CalculateFactorial, 7));

main块中,我们使用post()函数在io_service对象上发布三个函数对象。我们在初始化五个工作线程之后就这样做了。但是,因为我们在每个线程内部调用io_service对象的run()函数,所以io_service对象的工作将运行。这意味着post()功能将发挥作用。

现在,让我们运行post.cpp并看看这里发生了什么:

Using the post() function

正如我们在前面截图的输出中所看到的,程序运行线程池中的线程,完成一个线程后,从io_service对象调用post()函数,直到调用完所有三个post()函数和所有五个线程。然后,它计算每三个 n 数的阶乘。得到worker.reset()功能后,通知工作已经完成,然后通过threads.join_all()功能加入所有线程。

使用调度()功能

现在,让我们通过检查dispatch()功能来给io_service功能一些工作。我们仍将使用mutexbind.cpp文件作为我们的基本代码,我们将对其稍加修改,使其如下所示:

/* dispatch.cpp */
#include <boost/asio.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <iostream>

boost::mutex global_stream_lock;

void WorkerThread(boost::shared_ptr<boost::asio::io_service> iosvc) {
  global_stream_lock.lock();
  std::cout << "Thread Start.\n";
  global_stream_lock.unlock();

  iosvc->run();

  global_stream_lock.lock();
  std::cout << "Thread Finish.\n";
  global_stream_lock.unlock();
}

void Dispatch(int i) {
  global_stream_lock.lock();
  std::cout << "dispath() Function for i = " << i <<  std::endl;
  global_stream_lock.unlock();
}

void Post(int i) {
  global_stream_lock.lock();
  std::cout << "post() Function for i = " << i <<  std::endl;
  global_stream_lock.unlock();
}

void Running(boost::shared_ptr<boost::asio::io_service> iosvc) {
  for( int x = 0; x < 5; ++ x ) {
    iosvc->dispatch(boost::bind(&Dispatch, x));
    iosvc->post(boost::bind(&Post, x));
    boost::this_thread::sleep(boost::posix_time::milliseconds(1000));
  }
}

int main(void) {
  boost::shared_ptr<boost::asio::io_service> io_svc(
    new boost::asio::io_service
  );

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

  global_stream_lock.lock();
  std::cout << "The program will exit automatically once all work has finished." << std::endl;
  global_stream_lock.unlock();

  boost::thread_group threads;

  threads.create_thread(boost::bind(&WorkerThread, io_svc));

  io_svc->post(boost::bind(&Running, io_svc));

  worker.reset();

  threads.join_all();

  return 0;
}

将前面的代码命名为dispatch.cpp,使用以下命令编译它:

g++ -Wall -ansi -I ../boost_1_58_0 dispatch.cpp -o dispatch -L ../boost_1_58_0/stage/lib -l boost_system-mgw49-mt-1_58 -l ws2_32 -l boost_thread-mgw49-mt-1_58

现在,让我们运行程序以获得以下输出:

Using the dispatch() function

post.cpp文件不同的是,在dispatch.cpp文件中,我们只创建了一个工作线程。另外,我们添加了两个函数,dispatch(),post()来理解这两个函数之间的区别:

iosvc->dispatch(boost::bind(&Dispatch, x));
iosvc->post(boost::bind(&Post, x));

如果我们在Running()函数中查看前面的代码片段,我们期望得到dispatch()post()函数之间的有序输出。然而,当我们看到输出时,我们发现结果是不同的,因为首先调用dispatch()函数,然后调用post()函数。发生这种情况是因为dispatch()函数可以从当前工作线程调用,而post()函数必须等到工作线程的处理程序完成后才能调用。换句话说,dispatch()函数的事件可以从当前工作线程执行,即使有其他未决事件排队,而post()函数的事件必须等到处理程序完成执行后才能被允许执行。

总结

我们可以使用两个函数来获取为我们工作的io_service对象:run()poll()成员函数。run()功能阻止程序,因为它必须等待我们分配给它的工作,而poll()功能不阻止程序。当我们需要对io_service对象进行一些工作时,我们只需根据需要使用poll()run()功能,然后根据需要调用post()dispatch()功能。post()函数用于命令io_service对象运行给定的处理程序,但不允许处理程序由io_service对象从该函数内部调用。而dispatch()函数用于调用当前正在调用run()poll()函数的线程中的处理程序。dispatch()post()功能的根本区别在于dispatch()功能只要有可能就马上完成工作,而post()功能总是将工作排队。

我们发现了io_service对象,如何运行它,以及如何给它一些工作。现在,让我们进入下一章,了解更多关于Boost.Asio库的信息,我们离创建我们的网络编程又近了一步。