You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C GUI(如SDL2)中用libcurl发起GET请求不阻塞主循环的方法

问题背景与疑问

我更熟悉网站开发,常用JS的fetch发起异步请求,不会阻塞代码或UI渲染:

fetch().then(resp => {
  /* 处理响应时不阻塞代码执行或UI渲染 */
});

最近尝试用C开发SDL2应用,已经完成窗口创建、主循环和资源清理的基础代码:

int sdlCode = SDL_Init(SDL_INIT_VIDEO);
if (sdlCode < 0) {
    printf("Could not initialize SDL: %s :(", SDL_GetError());
    return sdlCode;
}
SDL_Window *win = SDL_CreateWindow(
    "SDL Tutorial",
    SDL_WINDOWPOS_UNDEFINED,
    SDL_WINDOWPOS_UNDEFINED,
    800,
    600,
    SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE
);
if (win == NULL) {
    printf("Could not create SDL window: %s :(", SDL_GetError());
    return -1;
}
SDL_Surface *screenSurface;
SDL_Event e;
SDL_Rect r;
int w, h;
bool repaint = true;
do {
    SDL_PollEvent(&e);
    if (repaint) {
        screenSurface = SDL_GetWindowSurface(win);
        SDL_GetWindowSize(win, &w, &h);
        r = (SDL_Rect) {0, 0, w, h };
        SDL_FillRect(screenSurface, &r, 0xFFFFFF);
        SDL_UpdateWindowSurface(win);
    }
    repaint = e.type == SDL_WINDOWEVENT && e.window.event == SDL_WINDOWEVENT_RESIZED;
} while (e.type != SDL_QUIT);
SDL_DestroyWindow(win);
SDL_Quit();
return 0;

现在需要在窗口resize等事件触发时调用在线API,用libcurl的curl_easy_perform可以实现,但这个方法是阻塞的:

curl_easy_perform - perform a blocking network transfer

如果把请求放在主循环里,会导致窗口卡顿:

// [...]
CURL *curlCtx = curl_easy_init();
curl_easy_setopt(curlCtx, CURLOPT_URL, "https://httpbin.org/delay/2");
curl_easy_setopt(curlCtx, CURLOPT_WRITEFUNCTION, fwrite);
curl_easy_setopt(curlCtx, CURLOPT_WRITEDATA, stdout);
CURLcode resp;
do {
    // [...]
    if (repaint) {
        resp = curl_easy_perform(curlCtx);
        if (resp != CURLE_OK) printf("Curl request failed: %s :(", curl_easy_strerror(resp));
        // [...]
    }
    // [...]
} while (e.type != SDL_QUIT);
curl_easy_cleanup(curlCtx);
// [...]
return 0;

网上搜的方案要么是用curl_multi_perform处理并发,要么是SDL多线程,但我担心多线程的开销:

Here's the important part: a poorly made multithreaded program can perform worse than a single threaded program. Much worse. The fact is that multithreading inherently adds more overhead because threads then have to be managed. If you do not know the costs of using different multithreading tools, you can end up with code that is much slower than its single threaded equivalent.

所以有两个疑问:

  1. 解决这个阻塞问题必须用多线程吗?
  2. JS的fetch是怎么实现异步的?底层是创建单独线程,还是通过系统调用实现响应回调?

解答

1. 不是必须用多线程,有两种可行方案

方案一:使用libcurl的multi接口(非阻塞、单线程)

curl_multi系列接口支持非阻塞IO,可以把网络请求和SDL的主循环结合起来。原理是利用系统的IO多路复用(比如select/poll/epoll),让主循环在等待事件的同时,也能监听curl的网络套接字状态,一旦有数据可读/可写,就调用curl_multi_perform处理,不会阻塞整个UI循环。

大致实现步骤:

  • 初始化CURLM句柄,把你的CURL句柄添加进去
  • 在SDL主循环里,用curl_multi_fdset获取curl关注的文件描述符集合
  • 用SDL_WaitEventTimeout或者select等待事件,同时设置超时时间,避免死等
  • 当有事件触发或者超时后,调用curl_multi_perform处理网络请求的进度
  • 用curl_multi_info_read检查是否有请求完成,然后处理响应

这种方式不用开额外线程,不会有线程管理的开销,适合简单的异步请求场景。

方案二:轻量多线程(可控开销)

如果觉得curl_multi的逻辑太复杂,也可以用多线程,但只要合理设计,开销其实很小。比如:

  • 只给网络请求开一个单独的工作线程,不要频繁创建销毁线程(可以用线程池或者复用单个线程)
  • 线程里只处理网络请求,完成后通过SDL的事件队列(SDL_PushEvent)把结果发送给主循环,主循环再更新UI
  • 避免在工作线程里直接操作SDL的UI对象(SDL大部分API不是线程安全的,必须在主线程调用)

只要不是滥用线程(比如每个请求开一个线程),这种方式的开销几乎可以忽略,代码逻辑也更直观。

2. JS的fetch是基于事件循环和系统异步IO实现的

JS的fetch并不是给每个请求开单独线程,而是依赖浏览器/Node.js的事件循环和操作系统的异步IO机制:

  • 底层调用系统的异步IO接口(比如Linux的epoll、Windows的IOCP、macOS的kqueue),这些接口可以同时监听多个IO操作,不需要线程阻塞等待
  • 当网络请求的响应就绪时,系统会通知事件循环,事件循环再把对应的回调函数放到任务队列里,等当前执行栈清空后,就会执行回调
  • 整个过程都是单线程(JS主线程)处理回调,线程只在有任务的时候运行,不会空转,所以没有额外的线程管理开销

简单说,fetch的异步是IO多路复用+事件驱动,不是靠多线程实现的。


内容的提问来源于stack exchange,提问作者Zalaxci

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 04:19:54