直接多线程fwrite同一文件会导致数据覆盖、错位或EBADF错误,因fwrite非原子且std::ofstream不线程安全;必须用mutex全程保护打开、写入、flush等整套操作。

为什么不能直接用 fopen + 多线程写同一个文件
多个线程同时 fwrite 到同一文件偏移位置,会触发未定义行为:数据覆盖、错位、甚至 errno = EBADF(文件描述符被意外关闭)。C++ 标准库的 std::ofstream 也不是线程安全的写入器——它不保证多线程调用 operator 或 <code>write() 时的原子偏移控制。
正确做法是每个线程只负责一段固定字节范围,并通过 pwrite(POSIX)或 SetFilePointerEx + WriteFile(Windows)做**偏移写入**,绕过文件指针竞争。
- Linux/macOS 必须用
open(..., O_RDWR | O_CREAT)获取原始 fd,再用pwrite(fd, buf, size, offset) - Windows 需用
CreateFile(..., FILE_FLAG_NO_BUFFERING)(可选),配合SetFilePointerEx和WriteFile - 不要用
std::ofstream的seekp+write—— 它底层仍依赖共享文件指针,且seekp不是原子操作
如何获取文件总大小并划分分块区间
HTTP 下载必须先发 HEAD 或 GET 请求带 Range: bytes=0-0,读取响应头中的 Content-Length。注意:有些服务器不支持 HEAD,或返回 Transfer-Encoding: chunked(此时无法预知总长,分块下载不可行)。
拿到总大小 total_size 后,按线程数 N 均分区间:[0, chunk_size), [chunk_size, 2*chunk_size), ..., 最后一块向上取整。
立即学习“C++免费学习笔记(深入)”;
- 避免最后一块越界:最后一块的
end = std::min(offset + chunk_size, total_size) - chunk_size 建议设为 1MB~8MB;太小增加 HTTP 开销,太大降低并发粒度
- 务必检查
Content-Length是否存在且为数字,否则中止分块逻辑,退化为单流下载
每个线程如何安全发起 Range 请求并写入指定偏移
每个线程持有一个独立的 HTTP 客户端实例(如 libcurl),设置 Range header 并启用 CURLOPT_HTTPGET,再用底层系统调用写入对应磁盘偏移。
示例关键片段(Linux):
int fd = open("out.bin", O_RDWR | O_CREAT, 0644);
// 线程内:
long long start = 1024 * 1024; // 第二块起始
long long len = 1024 * 1024;
std::vector<char> buf(len);
// ... curl write callback 填满 buf ...
ssize_t written = pwrite(fd, buf.data(), buf.size(), start);
if (written != (ssize_t)buf.size()) {
// 写入失败:磁盘满 / 权限不足 / fd 无效
}
- libcurl 必须设
CURLOPT_WRITEFUNCTION自定义回调,把 body 数据存入预分配的std::vector<char> - 不要在回调里直接
pwrite—— 可能触发锁或内存重分配,先缓存再写 - 所有线程共用一个
fd没问题,pwrite是线程安全的 - Windows 上等价的是
SetFilePointerEx(hFile, liOffset, nullptr, FILE_BEGIN)+WriteFile,注意liOffset是LARGE_INTEGER
下载完成后如何校验完整性
分块下载最大的风险不是写错位置,而是某一块请求失败却没重试,导致文件局部空白。仅靠 curl_easy_perform 返回值不够——它可能成功返回 HTTP 206,但 body 少字节(如网络中断、服务端 bug)。
- 每块写入后,调用
fsync(fd)(Linux)或FlushFileBuffers(hFile)(Windows)确保落盘 - 下载完毕后,重新
mmap整个文件,用SHA256或MD5对比服务端提供的ETag或Content-MD5header - 若校验失败,记录失败块区间,只重下那几块(而非全量重试)
- 不要依赖
stat().st_size是否等于Content-Length—— 文件可能写了一半就崩溃,大小对得上但内容错乱
分块下载真正难的不是并发,而是错误传播的隔离和恢复粒度。一块失败不该阻塞其他块,但也不能让它静默污染最终文件。


















