【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() {
    // 初始化epoll
    int epoll_fd = epoll_create1(0);
    struct epoll_event event, events[10]; // 假设我们监视最多10个事件

    // 初始化线程池
    thread_pool_init(4);

    // 添加监听的文件描述符到epoll
    event.events = EPOLLIN; // 监听读事件
    event.data.fd = /* 监听的文件描述符 */;
    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, event.data.fd, &event);

    // 事件循环
    while (1) {
        // 等待事件发生,无需阻塞IO
        int 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);

            // 注册新的连接到epoll
            struct 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返回,然后这些就绪的描述符的数据处理任务被分配到线程池中去执行。

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何建立标准且有效的评审流程?6个重点

为了进一步提高项目质量&#xff0c;项目评审管理需要遵循一定的标准化流程。而建立标准且有效的评审流程&#xff0c;能够快速提高项目质量和效率&#xff0c;优化团队协作&#xff0c;降低风险&#xff0c;提高项目成功率。如果组织没有建立起标准化的评审流程&#xff0c;就…

JAVAEE初阶相关内容第二十弹--HTTP协议【续集】

写在前&#xff1a;在前一篇博客中我们初步掌握了HTTP(超文本传输协议)的相关知识【点击跳转】&#xff0c;认识了HYYP协议的工作过程&#xff0c;掌握抓包工具Fiddler的使用。在“方法”中重点需要理解“GET”方法与“POST”方法的格式与内容&#xff0c;并了解了请求“报头”…

万众期盼的剪辑新功能来了 会声会影2024旗舰版焕新登场

会声会影2024全新升级来袭&#xff0c;Corel公司这次为用户带来了多项功能更新&#xff0c;软件风格整体更偏向于“轻松剪辑&#xff0c;快速出片”。会声会影的本次更新还是很令人惊喜的&#xff0c;在各种人工智能算法的加持下&#xff0c;用户只需要进行几步简单地设置&…

sublim安装Autoprefixer插件

有时候在写css样式的时候&#xff0c;分不清哪些属性需要前缀&#xff0c;哪些不需要写前缀&#xff0c;sublime text这款编辑器下安装autoprefixer这款插件可以省去很多问题&#xff0c;写起来也很方便。1 确保系统已经安装node.js 可直接去官网上下载并安装&#xff0c;我的系…

网络实训模拟考察题目和答案(华为eNSP综合实验考试)

拓扑中四个交换机五个路由器&#xff0c;共九个设备 答案是对应的九个脚本&#xff08;从设备命名到保存&#xff09; 全部复制粘贴后&#xff0c;从PC1、PC2都是能Ping通服务器的&#xff08;保及格&#xff09;&#xff0c;其他要求没检查 题目 VLAN信息 设备名称端口链路…

【设计模式之美】面向对象分析方法论与实现(一):需求分析方法论

文章目录 一. 需求举例二. 对案例进行需求分析1. 第一轮基础分析2. 第二轮分析优化3. 第三轮分析优化4. 第四轮分析优化5. 最终确定需求 三. 小结 本文主要描述&#xff1a; 面向对象的需求分析方法论 一. 需求举例 假设&#xff0c;你正在参与开发一个微服务。微服务通过 HTT…

软件测试|SQL JOIN的用法,你会了吗?

SQL JOIN 是在关系型数据库中常用的操作&#xff0c;用于将两个或多个表中的数据合并起来&#xff0c;以满足查询需求。本文将介绍 SQL JOIN 的基本概念、不同类型的 JOIN&#xff0c;以及使用示例。 SQL JOIN 的概念 在关系型数据库中&#xff0c;数据通常分布在多个表中&am…

【C语言】关闭socket需要包含的头文件

一、问题 linux系统&#xff0c;包含了头文件<sys/socket.h>&#xff0c; 警告 warning: implicit declaration of function ‘close’; did you mean ‘pclose’? [-Wimplicit-function-declaration] close(sockclient); ^~~~~ pclose 二、解决 在 Linux 系统下…

网络安全是什么?一文认识网络安全

一、网络安全 1.概念 网络安全从其本质上讲就是网络上的信息安全&#xff0c;指网络系统的硬件、软件及数据受到保护。不遭受破坏、更改、泄露&#xff0c;系统可靠正常地运行&#xff0c;网络服务不中断。 &#xff08;1&#xff09;基本特征 网络安全根据其本质的界定&#…

labview 与三菱FX 小型PLC通信(OPC)

NI OPC服务器与三菱FX3U PLC通讯方法 一、新建通道名称为&#xff1a;MIT 二、选择三菱FX系列 三、确认端口号相关的参数&#xff08;COM端&#xff1a;7.波特率&#xff1a;9600&#xff0c;数据位&#xff1a;7&#xff0c;校验&#xff1a;奇校验&#xff0c;停止位&#xf…

码农的周末日常---2024/1/6

上周总结 按照规划进行开发&#xff0c;处事不惊&#xff0c;稳稳前行 2024.1.6 天气晴 温度适宜 AM 睡觉前不建议做决定是真的&#xff0c;昨天想着睡到中午&#xff0c;今天九点多醒了&#xff0c;得了&#xff0c;不想睡了 日常三连吧&#xff0c;…

面试官:String为什么要设计为不可变类

程序员的公众号&#xff1a;源1024&#xff0c;获取更多资料&#xff0c;无加密无套路&#xff01; 最近整理了一份大厂面试资料《史上最全大厂面试题》&#xff0c;Springboot、微服务、算法、数据结构、Zookeeper、Mybatis、Dubbo、linux、Kafka、Elasticsearch、数据库等等 …

@JsonFormat与@DateTimeFormat

JsonFormat注解很好的解决了后端传给前端的格式&#xff0c;我们通过使用 JsonFormat可以很好的解决&#xff1a;后台到前台时间格式保持一致的问题 其次&#xff0c;另一个问题是&#xff0c;我们在使用WEB服务的时&#xff0c;可 能会需要用到&#xff0c;传入时间给后台&am…

数据处理四 基于图像hash进行数据整理(删除重复图片、基于模版查找图片)

一、背景知识 1.1 什么是hash Hash&#xff0c;一般翻译做“散列”&#xff0c;也有直接音译为“哈希”的&#xff0c;基本原理就是把任意长度的输入&#xff0c;通过Hash算法变成固定长度的输出。这个映射的规则就是对应的Hash算法&#xff0c;而原始数据映射后的二进制串就…

如何使用免费的ZeroSSL证书保护您的网站

使用 ZeroSSL 在您的站点上轻松实施 SSL。我们的指南涵盖了免费证书设置&#xff0c;确保安全和加密的用户连接。 如今&#xff0c;保护您的网站不仅是一种建议&#xff0c;而且是一种必需品。这就是SSL证书发挥作用的地方。它们对用户浏览器和网站之间传输的数据进行加密&…

Golang高质量编程与性能调优实战

1.1 简介 高质量&#xff1a;编写的代码能否达到正确可靠、简洁清晰的目标 各种边界条件是否考虑完备异常情况处理&#xff0c;稳定性保证易读易维护 编程原则 简单性 消除多余的重复性&#xff0c;以简单清晰的逻辑编写代码不理解的代码无法修复改进 可读性 代码是写给人看…

云原生学习系列之基础环境准备(单节点安装kubernetes)

一、环境要求 操作系统CentOS 7.x-86_x64 硬件配置&#xff1a;内存2GB或2G&#xff0c;CPU 2核或CPU 2核&#xff0c;需要在虚拟机中提前设置好&#xff0c;不然后续会报错 二、系统初始化 1、设置主机名 # 在master节点执行 hostnamectl set-hostname master01 2、配置主…

时间序列预测 — LSTM实现多变量多步负荷预测(Tensorflow):多输入多输出

目录 1 数据处理 1.1 导入库文件 1.2 导入数据集 ​1.3 缺失值分析 2 构造训练数据 3 LSTM模型训练 4 LSTM模型预测 4.1 分量预测 4.2 可视化 1 数据处理 1.1 导入库文件 import time import datetime import pandas as pd import numpy as np import matplotlib.p…

Kafka消息阻塞:拯救面试的八大终极解决方案!

大家好&#xff0c;我是小米&#xff0c;一个对技术充满热情的90后程序员。最近在准备社招面试的过程中&#xff0c;遇到了一个超级有挑战性的问题&#xff1a;“Kafka消息阻塞怎么解决&#xff1f;”今天&#xff0c;我就来和大家一起深入剖析这个问题&#xff0c;分享我在解决…

Python从入门到网络爬虫(MySQL链接)

前言 在实际数据分析和建模过程中&#xff0c;我们通常需要从数据库中读取数据&#xff0c;并将其转化为 Pandas dataframe 对象进行进一步处理。而 MySQL 数据库是最常用的关系型数据库之一&#xff0c;因此在 Python 中如何连接 MySQL 数据库并查询数据成为了一个重要的问题…