【C语言】Linux实现高并发处理的过程

一、实现高并发的几种策略

C语言本身并没有内建的多线程支持(新版C语言支持,但用得不多),但是在多数操作系统中,可以使用库来实现多线程编程。例如,在POSIX兼容系统上,可以使用 pthreads 库来创建和管理线程。然而,传统的多线程存在着资源限制,比如每个线程都需要独立的堆栈空间,上下文切换开销大,线程数量多时还会导致竞争情况加剧。
为了兼顾高并发和高性能,可以采取以下几种策略:
1. 线程池(Thread Pools):创建一个线程池来管理一定数量的线程,避免了频繁创建和销毁线程的开销,可以复用线程处理多个任务。
2. 事件驱动(Event-Driven): 使用事件驱动(如使用select/poll/epoll/kqueue等)的非阻塞IO模型可以减少线程数目和上下文切换的开销,同时能够处理大量并发连接。


3. 异步IO(Asynchronous I/O): 利用操作系统级别的异步IO接口,比如posix的aio系列函数,这样IO操作不会阻塞线程。
4. 协程(Coroutines):协程是一种用户态的轻量级线程,协程库(如libco、libtask)可以在用户空间进行上下文切换,拥有极低的切换成本,并能够在单线程内实现高并发。
5. 使用其他并发模型:比如Go语言中的Goroutines,Erlang语言中的Actor模型,它们都是设计上为并发而生,能够实现高性能的并发处理。
6. 硬件加速:在某些应用场景中,使用专用硬件或者利用GPU并行计算能力也能大幅提高并发处理性能。
每种方法有各自的优缺点和适用的场景,实际选择时需要根据应用需求、系统特性和资源限制综合考虑。在需要处理大规模并发连接时,通常会使用事件驱动和异步IO结合的方式来实现高效的并发处理。

二、异步IO(Asynchronous I/O)和同步IO(Synchronous I/O)

异步IO(Asynchronous I/O)是一种让程序启动一个IO操作以后不必等待其完成就能继续执行其他任务的技术。同步IO(Synchronous I/O),在执行IO操作时会阻塞当前线程,直到IO操作完成。

下面举例来说明两者之间的区别:

同步IO

在同步IO模型中,应用程序执行一个IO操作,如从文件读取数据或写入数据到文件,然后等待操作的完成。在这个过程中,应用程序被阻塞,不能执行其他任何操作。只有当IO请求完成,并且数据被复制到应用程序的缓冲区之后,应用程序才可以继续执行。

例如,这是一个简化的同步IO读操作的代码示例:

FILE* file = fopen("example.txt", "r");
if (file) {char buffer[1024];size_t bytes_read = fread(buffer, sizeof(char), sizeof(buffer), file);if (bytes_read > 0) {// 处理读取到的数据}fclose(file);
}

在这个例子中,`fread` 函数将会阻塞直到指定数量的字节被读取到 buffer 中或遇到文件结尾。

异步IO

在异步IO模型中,应用程序发出IO操作请求并直接返回,可以继续执行其他操作。当IO操作实际完成后,应用程序会以某种方式被通知,例如通过回调函数、IO完成队列或者信号等,这样应用程序可以处理IO操作的结果。

例如这是一种异步读操作的伪代码示例:

void io_completion_callback(IOOperation *op) {// 处理异步操作完成的数据
}void initiate_async_read(const char* file_path) {// 设置异步读取操作,指定完成后的回调函数IOOperation *op = setup_async_read(file_path, io_completion_callback);// 发起异步读取操作,立即返回start_async_io(op);
}// 应用程序继续执行,而IO操作在后台进行

在使用异步IO时,应用程序不需要在IO上阻塞等待,能够更好地利用CPU和IO设备的性能,特别是在需要大量IO操作和高并发处理的程序中。

在UNIX/Linux系统中,支持异步IO的API包括`aio_read`、`aio_write`以及IO复用的系统调用如`select`、`poll`和`epoll`。这些API提供了不同的异步处理机制,使得应用程序可以在不阻塞的情况下监控多个IO操作的状态。

三、简单的线程池

线程池的基本思路是预先创建一定数量的线程,并将它们放入等待队列中。每当有新的任务来临时,线程池会选择一个空闲线程去执行任务,执行完成后线程再次回到等待队列中。线程池通常有以下几个关键的组成部分:
- 任务队列(task queue)
- 锁和条件变量 (用于同步)
- 工作线程集合
- 管理线程池的API(创建、销毁线程池,加入任务等)

为了实现一个简单的线程池,我们需要一个工作队列来存放待处理的任务,以及一组工作线程来执行这些任务。下面是一个简易的线程池实现的示例,这个实现将会提供`thread_pool_init`、`thread_pool_enqueue`和`thread_pool_destroy`这三个函数。

这里的代码只是一个例子,不考虑所有边界条件和潜在的同步问题。在实际应用中,需要考虑线程同步机制、错误处理以及资源的正确释放等问题。

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>
#include <sys/queue.h>// 定义任务结构体
typedef struct task {void (*function)(void *arg);void *arg;TAILQ_ENTRY(task) entries; // 使用TAILQ宏定义队列元素
} task_t;// 定义任务队列
typedef TAILQ_HEAD(taskhead, task) taskhead_t;// 定义线程池结构体
typedef struct thread_pool {pthread_t *threads;int thread_count;taskhead_t task_queue;pthread_mutex_t lock;pthread_cond_t cond;bool stop;
} thread_pool_t;// 线程池全局变量
thread_pool_t pool;// 线程池工作线程
void *thread_pool_worker(void *arg) {while (1) {pthread_mutex_lock(&pool.lock);// 等待直到有任务到来或者销毁线程池while (TAILQ_EMPTY(&pool.task_queue) && !pool.stop) {pthread_cond_wait(&pool.cond, &pool.lock);}if (pool.stop) {break;}task_t *task = TAILQ_FIRST(&pool.task_queue);TAILQ_REMOVE(&pool.task_queue, task, entries);pthread_mutex_unlock(&pool.lock);// 执行任务task->function(task->arg);free(task);pthread_mutex_lock(&pool.lock);}pthread_mutex_unlock(&pool.lock);return NULL;
}// 初始化线程池
void thread_pool_init(int num_threads) {pool.threads = malloc(sizeof(pthread_t) * num_threads);pool.thread_count = num_threads;TAILQ_INIT(&pool.task_queue);pthread_mutex_init(&pool.lock, NULL);pthread_cond_init(&pool.cond, NULL);pool.stop = false;for (int i = 0; i < num_threads; i++) {pthread_create(&pool.threads[i], NULL, thread_pool_worker, NULL);}
}// 添加任务到线程池队列
void thread_pool_enqueue(void (*function)(void*), void *arg) {task_t *task = malloc(sizeof(*task));task->function = function;task->arg = arg;pthread_mutex_lock(&pool.lock);TAILQ_INSERT_TAIL(&pool.task_queue, task, entries);pthread_cond_signal(&pool.cond);pthread_mutex_unlock(&pool.lock);
}// 销毁线程池
void thread_pool_destroy() {// 停止所有线程池工作线程pool.stop = true;pthread_cond_broadcast(&pool.cond);// 等待所有线程完成for (int i = 0; i < pool.thread_count; i++) {pthread_join(pool.threads[i], NULL);}// 清理资源pthread_mutex_destroy(&pool.lock);pthread_cond_destroy(&pool.cond);while (!TAILQ_EMPTY(&pool.task_queue)) {task_t *task = TAILQ_FIRST(&pool.task_queue);TAILQ_REMOVE(&pool.task_queue, task, entries);free(task);}free(pool.threads);
}

四、线程池和异步IO结合来实现

要在C语言中实现线程池和异步IO结合来实现高并发和高性能,可以通过以下几个步骤来操作:

1. 创建线程池:

首先,需要先创建一个线程池,这通常涉及到预先分配一定数量的线程,并且维护一个任务队列。

每个线程将从任务队列中取出任务来执行。通常还需要同步机制(如条件变量和互斥锁)来保护任务队列,以防止多个线程同时对队列进行操作。

2. 使用异步IO:

然后,应用程序主线程利用异步IO机制(如Linux下的epoll,Windows下的IOCP)来监视IO事件,当IO事件就绪时,不进行实际的读写操作,而是将这个任务放入线程池的任务队列。

异步IO事件可以通知程序某个IO操作(如网络数据的读或写)可以开始,并不会实际阻塞线程。

3. 任务处理:

当异步IO事件通知程序数据准备好了后,任务会被提交到线程池的任务队列中。

工作线程从队列中取出任务,执行实际的IO处理,如读取数据、进行业务处理和准备响应数据。

4. 完成异步任务:

处理完成后,工作线程可以继续使用异步IO机制来进行响应的发送,或者将完成的数据返回给主线程,由主线程统一发送。

5. 一个简化的例子

如下,以Linux平台为例,使用epoll和线程池:

#include <sys/epoll.h>
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>// 假设这里已经实现了一个线程池,和线程池相关的函数
// 初始化线程池
void thread_pool_init(int num_threads);
// 将任务添加到线程池队列
void thread_pool_enqueue(void (*task_function)(void*), void* task_data);
// 销毁线程池
void thread_pool_destroy();// 异步IO任务执行函数
void async_io_task(void* data) {int fd = *(int*)data;char buffer[1024];// 实际的IO操作,读取数据ssize_t bytes_read = read(fd, buffer, sizeof(buffer));// 进行业务处理(假设处理完毕,准备响应)// 发送响应(假设直接回写数据)write(fd, buffer, bytes_read);// 关闭文件描述符close(fd);
}int main() {// 初始化epollint epoll_fd = epoll_create1(0);struct epoll_event event, events[10]; // 假设我们监视最多10个事件// 初始化线程池thread_pool_init(4);// 添加监听的文件描述符到epollevent.events = EPOLLIN; // 监听读事件event.data.fd = /* 监听的文件描述符 */;epoll_ctl(epoll_fd, EPOLL_CTL_ADD, event.data.fd, &event);// 事件循环while (1) {// 等待事件发生,无需阻塞IOint n = epoll_wait(epoll_fd, events, 10, -1);for (int i = 0; i < n; i++) {if (events[i].events & EPOLLIN) {// 异步IO准备就绪,将任务提交到线程池thread_pool_enqueue(async_io_task, &(events[i].data.fd));}}}// 销毁线程池和关闭epoll文件描述符thread_pool_destroy();close(epoll_fd);return 0;
}

上述代码是一个高度简化的框架,真实的环境需要处理更多的细节,比如错误处理、动态资源管理、线程池和任务队列的具体实现等。此代码仅用于演示大体的结构和思路。

这种结合了线程池和异步IO的模型能够很好地平衡系统资源,同时支持大量客户端并发连接和IO操作,适用于例如高性能的网络服务器。

五、epoll处理网络连接的例子

在Linux平台,epoll是一个高效的事件通知机制,经常用于处理大量并发的网络连接。与其前辈select和poll相比,epoll`过一种称作“事件通知机制”的方式来减少无谓的轮询,并能够伸缩到数以万计的文件描述符。

以下是一个简化的例子,展示了如何组合epoll和线程池来处理大量的并发网络连接:

首先,创建一个监听socket,并且使用epoll_create来创建一个epoll实例:

int listen_fd = socket(AF_INET, SOCK_STREAM, 0);// 设置为非阻塞模式
int flags = fcntl(listen_fd, F_GETFL, 0);
fcntl(listen_fd, F_SETFL, flags | O_NONBLOCK);// 绑定和监听
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
bind(listen_fd, (struct sockaddr *)&addr, sizeof(addr));
listen(listen_fd, SOMAXCONN);// 创建epoll实例
int epoll_fd = epoll_create1(0);

然后在`listen_fd`上注册EPOLLIN事件:

struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = listen_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev);

接下来是工作循环。在这个循环中,我们会调用epoll_wait来等待事件的发生。对于每个就绪的socket,根据socket的类型(监听socket或者连接socket)进行不同的处理:

#define MAX_EVENTS 1024
struct epoll_event events[MAX_EVENTS];
while(1) {int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);for(int i = 0; i < nfds; ++i) {if(events[i].data.fd == listen_fd) {// 处理新的连接struct sockaddr_in client_addr;socklen_t client_addr_len = sizeof(client_addr);int client_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &client_addr_len);// 设置为非阻塞模式int flags = fcntl(client_fd, F_GETFL, 0);fcntl(client_fd, F_SETFL, flags | O_NONBLOCK);// 注册新的连接到epollstruct epoll_event client_ev;client_ev.events = EPOLLIN | EPOLLET;  // ET模式client_ev.data.fd = client_fd;epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &client_ev);} else {// 交给线程池处理即将读取的数据// 此处为了简化示例,我们假设使用了某个线程池enqueue的方法加入任务// 实际上应该使用像libuv之类的库或自定义线程池来处理任务thread_pool.enqueue([events, i]() {// 读取数据char buffer[1024];int n = read(events[i].data.fd, buffer, sizeof(buffer));// 处理数据// ...// 响应客户端// ...});}}
}

上面的代码结合了epoll和线程池,通过事件驱动提供了一个非阻塞的网络服务模型。当有新的连接到来时,accept会生成新的socket文件描述符,并将其加入到epoll实例进行监控。准备好读取数据的文件描述符将会被epoll_wait返回,然后这些就绪的描述符的数据处理任务被分配到线程池中去执行。

在实际的应用环境中,还需要处理各种网络编程中的细节问题(错误处理、客户端关闭连接的情况、资源回收、安全性考虑等),并且需要维护高效的线程池实现。

代码中的线程池并未具体实现,需要根据实际需要选择或实现一个线程池。此外,实际编码中还需要考虑重入性、异常安全性、内存泄漏问题等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/234711.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

FindMy技术用于键盘

键盘是我们生活中不可或缺的输入工具&#xff0c;是人与计算机之间沟通的桥梁&#xff0c;无论是编写文档、浏览网页、玩游戏、或是进行复杂的数据分析&#xff0c;键盘都在其中发挥着关键的作用。此外&#xff0c;键盘还是各种软件的快捷键操作的关键。通过熟练地运用快捷键&a…

vue-vben-admin 与.net core 结合实例 【自学与教学 小白教程】---第3节

ue-vben-admin 与.net core 结合实例 这里计划使用.net core 作为后端 。目标&#xff1a;打造好看 易用 开箱即用 的netcore一体化框架。Vue Vben Admin For NetCore 取命 hcrain-vvadmin 我不是前端人员 但有时开发还是要写一些界面。 之前使用layui是时候 狠心升级下了。 …

Linux网络的命令和配置

目录 一、网络配置命令 1、配置和管理网络接口 1.1 ifconfig 1.2 ip 1.2.1 ip link 1.2.2 ip addr 1.3 修改网络接口名 1.3.1 临时修改网络接口名 1.3.2 永久修改网络接口名 1.4 永久配置单网卡 1.5 永久配置双网卡 1.6 ethtool 2、查看和设置主机中路由表信息…

“第四个中国人民警察节”细语

今&#xff08;2024年1月10日&#xff09;天&#xff0c;是第四个中国人民警察节&#xff0c;本“人民体验官”推广人民日报官方微博文化产品《一起致敬人民警察&#xff01;》。 图&#xff1a;来源“人民体验官”推广平台 笔者认同“平安的密码叫110”这个洽当比喻。因为人民…

开源了,免费使用GPT4(Windows/Linux/Mac 一键启动脚本)

开源了&#xff0c;免费使用GPT4&#xff08;Windows一键启动脚本&#xff09; 大家好&#xff0c;我打算每日花1小时来写一篇文章&#xff0c;这一小时包括文章主题思考和实现&#xff0c;连续日更几天&#xff0c;看看能不能被官方推荐。&#xff08;帮我点点赞哦&#xff5…

Java IO学习和总结(超详细)

一、理解 I/O 是输入和输出的简写&#xff0c;指的是数据在计算机内部和外部设备之间的流动。简单来说&#xff0c;当你从键盘输入数据、从鼠标选择操作&#xff0c;或者在屏幕上看到图像&#xff0c;这些都是 I/O 操作。它就像是计算机与外部世界沟通的桥梁&#xff0c;没有 I…

【思扬赠书 | 第2期】语义解析为何作为连接自然语言与机器智能的桥梁?

⛳️ 写在前面参与规则&#xff01;&#xff01;&#xff01; ✅参与方式&#xff1a;关注博主、点赞、收藏、评论&#xff0c;任意评论&#xff08;每人最多评论三次&#xff09; ⛳️本次送书1~4本【取决于阅读量&#xff0c;阅读量越多&#xff0c;送的越多】 文章目录 01 …

esp32UART串口外设(Arduino)

通用异步接收器/发送器 &#xff08;UART&#xff09; 介绍 通用异步接收器/发送器 &#xff08;UART&#xff09; 是一种硬件功能&#xff0c;它使用广泛采用的异步串行通信接口&#xff08;如 RS232、RS422 和 RS485&#xff09;处理通信&#xff08;即时序要求和数据成帧&…

小白兼职做抖店,一个月能做到5000利润吗?抖店现状问题解答

我是王路飞。 全职状态下做抖店的话&#xff0c;精细化操作的单店月利润保守数据在10000-30000&#xff08;该数据来自醒醒团队内部学员一般水平下月营收数据均值&#xff0c;仅供参考&#xff09; 但如果是新手小白&#xff08;没有电商经验&#xff09;&#xff0c;且是兼职…

ES-极客学习第二部分ES 入门

基本概念 索引、文档、节点、分片和API json 文档 文档的元数据 需要通过Kibana导入Sample Data的电商数据。具体参考“2.2节-Kibana的安装与界面快速浏览” 索引 kibana 管理ES索引 在系统中找到kibana配置文件&#xff08;我这里是etc/kibana/kibana.yml&#xff09; vim /…

spring boot 自动扫描Controller、Service、Component原理

项目里面为什么不加上ComponentScan("com.yym.*")注解&#xff0c;也能加载到子目录里面的Controller&#xff0c;Service&#xff0c;Component的bean呢&#xff1f; 启动类没有ComponentScan注解 SpringBootApplication public class BootStrap {public static v…

【unity】基于Obi的绳/杆蓝图、绳杆区别及其创建方法

绳索 是通过使用距离和弯曲约束将粒子连接起来而形成的。由于规则粒子没有方向(只有位置)&#xff0c;因此无法模拟扭转效应(维基百科)&#xff0c;绳子也无法保持其静止形状。然而&#xff0c;与杆不同的是&#xff0c;绳索可以被撕裂/劈开&#xff0c;并且可以在运行时改变其…

java碳排放数据信息管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 java Web碳排放数据信息管理系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环 境为TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为…

行为型设计模式——模板方法模式

学习难度&#xff1a;⭐ &#xff0c;比较常用 模板方法模式 在面向对象程序设计过程中&#xff0c;程序员常常会遇到这种情况&#xff1a;设计一个系统时知道了算法所需的关键步骤&#xff0c;而且确定了这些步骤的执行顺序&#xff0c;但某些步骤的具体实现还未知&#xff0…

【重学C语言】一、C语言简介

【重学C语言】一、C语言简介 什么是编程语言&#xff1f;编程语言 C语言发展史C语言标准变迁开发软件CLion安装步骤 VIsual Studio安装步骤 Clion 和 VS2022 绑定 电脑常识 什么是编程语言&#xff1f; 人类语言&#xff1a;语言就是人类进行沟通交流的表达方式&#xff0c;应…

鸿鹄云商B2B2C:JAVA实现的商家间直播带货商城系统概览

【saas云平台】打造全行业全渠道全场景的saas产品&#xff0c;为经营场景提供一体化解决方案&#xff1b;门店经营区域化、网店经营一体化&#xff0c;本地化、全方位、一站式服务&#xff0c;为多门店提供统一运营解决方案&#xff1b;提供丰富多样的营销玩法覆盖所有经营场景…

HarmonyOS 应用开发学习笔记 状态管理概述

移动端开发&#xff0c;最重要的一点就是数据的处理&#xff0c;并且正确的显示渲染UI。 变量在页面和组件、组件和组件之间有时候并不能实时共享&#xff0c;而有时候&#xff0c;又不需要太多的作用域&#xff08;节省资源&#xff09;&#xff0c;作用就需要根据不同场景&am…

Appium,多应用程序平台的 UI 自动化

Appium是一个开源的移动应用程序自动化测试工具&#xff0c;可以用于跨平台的UI自动化&#xff0c;包括iOS、Android、Web和Windows应用程序。它基于WebDriver协议&#xff0c;支持多种编程语言&#xff0c;如Java、Python、Ruby,Javascript、C#等。 Appium的设计理念是“一次…

ProtoBuf一些踩坑记录

一、Protobuf学习基础 学习的资料很多也很全&#xff0c;这里添加几个链接进行Protobuf的基础学习的链接&#xff0c;链接中的案例使用C编辑&#xff1a; 链接&#xff1a;Protobuf介绍及简单使用(上&#xff09;_google_protobuf_version-CSDN博客 Protobuf介绍及简单使用(下&…

[AutoSar]基础部分 RTE 03 C/S Port 同步/异步

目录 关键词平台说明一、C/S port interface 定义1.1在Davinci developer中的创建 二、同步调用和异步调用2.1 同步2.1.1同步code2.1.2同步处理时序图 2.2 异步2.2.1异步code2.2.2异步处理时序图2.2.2.1 poling2.2.2.2 waiting2.2.2.3none 三、server端的mapping到task详解 关键…