门户网站建设基础术语,上海传媒公司简介,专业网站建设企业网站制作,企业logo查询网hello #xff01;大家好呀#xff01; 欢迎大家来到我的Linux高性能服务器编程系列之两种高性能并发模式介绍#xff0c;在这篇文章中#xff0c;你将会学习到高效的创建自己的高性能服务器#xff0c;并且我会给出源码进行剖析#xff0c;以及手绘UML图来帮助大家来理解… hello 大家好呀 欢迎大家来到我的Linux高性能服务器编程系列之两种高性能并发模式介绍在这篇文章中你将会学习到高效的创建自己的高性能服务器并且我会给出源码进行剖析以及手绘UML图来帮助大家来理解希望能让大家更能了解网络编程技术 希望这篇文章能对你有所帮助大家要是觉得我写的不错的话那就点点免费的小爱心吧注这章对于高性能服务器的架构非常重要哟 导言 并发编程的目的是让程序“同时”执行多个任务。如果程序是计算密集型的并发编程并没有优势反而由于任务的切换使效率降低。但如果程序是I/O 密集型的比如经常读写文件访问数据库等则情况就不同了。由于I/O 操作的速度远没有CPU的计算速度快所以让程序阻塞于I/O 操作将浪费大量的CPU时间。如果程序有多个执行线程则当前被I/O操作所阻塞的执行线程可主动放弃CPU(或由操作系统来调度)并将执行权转移到其他线程。这样一来CPU 就可以用来做更加有意义的事情(除非所有线程都同时被I/O 操作所阻塞)而不是等待I/O操作完成因此CPU的利用率显著提升。 目录 一.半同步/半异步模式 1.1 什么是同步/异步
1.2 半同步/半异步 1.3 半同步/半反应堆模式变体
1.4 更高效的半同步/半异步模式 1.5 实例代码 一.半同步/半异步模式 1.1 什么是同步/异步 首先对于I/O模型中的同步和异步意思是区分内核向应用程序通知的是何种I/O事件是就绪事件还是完成事件以及让谁来完成任务是应用程序还是内核。而在并发编程中指程序按照顺序还是需要按照程序执行的需要由系统事件来驱动常见的系统事件有中断信号。
如下图 按照同步方式运行的线程称为同步线程按照异步方式运行的线程称为异步线程。显然异步线程的执行效率高实时性强这是很多嵌入式程序采用的模型。但编写以异步方式执行的程序相对复杂难于调试和扩展而且不适合于大量的并发。而同步线程则相反它虽然效率相对较低实时性较差但逻辑简单。因此对于像服务器这种既要求较好的实时性又要求能同时处理多个客户请求的应用程序我们就应该同时使用同步线程和异步线程来实现即采用半同步/半异步模式来实现。 1.2 半同步/半异步
在这个模式中同步线程用于处理客户逻辑异步线程用于处理I/O事件异步线程监听到客户请求后将其封装为请求对象并且插入请求队列中请求队列将通知某个工作在同步模式的工作线程来读取并且处理该对象线程选择使用Round Robin算法也可以通过条件变量和信号量来实现具体工作流程图如图所示 1.3 半同步/半反应堆模式变体
异步线程只有一个由主线程来充当。它负责监听所有socket上的事件。如果监听socket 上有可读事件发生即有新的连接请求到来主线程就接受之以得到新的连接socket,然后往epoll内核事件表中注册该socket上的读写事件。如果连接socket 上有读写事件发生即有新的客户请求到来或有数据要发送至客户端主线程就将该连接 socket插入请求队列中。所有工作线程都睡眠在请求队列上当有任务到来时它们将通过竞争(比如申请互斥锁)获得任务的接管权。这种竞争机制使得只有空闲的工作线程才有机会来处理新任务这是很合理的。
如图 其中主线程插入请求队列中的任务是就绪的连接socket 这说明半同步/半反应堆模式采用的事件处理模式是Reactor模式它要求工作线程自己从socket 上读取客户请求和往socket写入服务器应答。 这就是该模式的名称中“half-reactive”的含义。实际上半同步/半反应堆模式也可以使用模拟的Proactor事件处理模式即由主线程来完成数据的读写。在这种情况下主线程一般会将应用程序数据、任务类型等信息封装为一个任务对象然后将其(或者指向该任务对象的一个指针)插入请求队列。工作线程从请求队列中取得任务对象之后即可直接处理之而无须执行读写操作。 半同步/半反应堆模式存在如下缺点 1 主线程和工作线程共享请求队列。主线程往请求队列中添加任务或者工作线程从请求队列中取出任务都需要对请求队列加锁保护从而白白耗费CPU 时间。 2 每个工作线程在同一时间只能处理一个客户请求。如果客户数量较多而工作线程较少则请求队列中将堆积很多任务对象客户端的响应速度将越来越慢。如果通过增加工作线程来解决这一问题则工作线程的切换也将耗费大量CPU 时间。 1.4 更高效的半同步/半异步模式 主线程只管理监听socket 连接socket由工作线程来管理当有新的连接到来时主线程就接受之并将新返回的连接socket 派发给某个工作线程此后该新 socket上的任何I/O操作都由被选中的工作线程来处理直到客户关闭连接。
主线程向工作线程派发socket 的最简单的方式是往它和工作线程之间的管道里写数据。
工作线程检测到管道上有数据可读时就分析是否是一个新的客户连接请求到来。如果是则把该新 socket上的读写事件注册到自己的epoll内核事件表中。
每个线程(主线程和工作线程)都维持自己的事件循环它们各自独立地监听不同的事件。因此在这种高效的半同步/半异步模式中每个线程都工作在异步模式所以它并非严格意义上的半同步/半异步模式。 1.5 实例代码
异步线程逻辑处理 // 事件处理函数
void *handle_connection(void *socket_desc) {int sock *(int*)socket_desc;char *message;int len;// 接收客户端数据while((len read(sock, message, 1024)) 0) {printf(收到数据%s\n, message);// 发送响应write(sock, Hello, Client!, 14);memset(message, 0, 1024);}// 关闭套接字close(sock);return 0;
}
同步线程监听请求和分配任务给异步线程
// 循环处理事件while(1) {activity select(FD_SETSIZE, readfds, NULL, NULL, NULL);if ((activity 0) (errno ! EINTR)) {printf(select error);return -1;}if (FD_ISSET(sock, readfds)) {// 有新的客户端连接newsock accept(sock, (struct sockaddr *)cli_addr, (socklen_t*)clilen);printf(新的客户端连接%s\n, inet_ntoa(cli_addr.sin_addr));client_sockets[i] newsock;FD_SET(newsock, readfds);}for (i 0; i MAX_CLIENTS; i) {if (FD_ISSET(client_sockets[i], readfds)) {// 处理客户端数据new_sock malloc(1);*new_sock client_sockets[i];if (pthread_create(thread_id, NULL, handle_connection, (void*)new_sock) 0)return -1;}pthread_detach(thread_id);free(new_sock);FD_CLR(client_sockets[i], readfds);}}} 好啦到这里这篇文章就结束啦关于实例代码中我写了很多注释如果大家还有不懂得可以评论区或者私信我都可以哦 感谢大家的阅读我还会持续创造网络编程相关内容的记得点点小爱心和关注哟