摘要:Go语言编码实现了使用多线程并发下载文件的功能。
1. 获取系统的CPU核心数量,并将其作为线程数的参考值,并打印出来。
2. 定义要下载的文件的URL、线程数和输出文件名。
3. 使用`getFileSize()`函数获取文件大小,并打印出来。
4. 根据文件大小和线程数计算文件块大小,如果是最后一次线程的结尾设置为文件结尾,确保文件下载的完整性。
5. 创建一个等待组(`sync.WaitGroup`),用于确保所有下载完成后再合并文件。
6. 创建一个通道(`chunkPaths`),用于接收下载完成的文件块路径。
7. 启动多个goroutine并发下载文件块,每个goroutine负责下载指定范围的文件块。
8. 每个goroutine使用`downloadChunk()`函数下载文件块,并将下载完成的文件块路径发送到通道。
9. 等待所有下载完成,然后关闭通道,表示所有文件块都已下载完成。
10. 创建一个输出文件。
11. 使用`mergeChunk()`函数将下载的文件块合并到输出文件中,并在合并过程中打印合并成功或失败的信息。
12. 在合并完成后,删除临时的文件块。
13. 打印文件下载完成的消息。
源码通过并发下载文件块,利用多线程来加快文件下载的速度。每个线程负责下载文件的一个部分,下载完成后将文件块合并到最终的输出文件中。通过合理设置线程数,可以充分利用可用的CPU资源,提高下载效率。
请注意,代码中使用了`http`和`os`包来进行文件下载和操作,需要保证网络连接正常,并且有足够的权限来创建和删除文件。
package main
import (
"fmt"
"io"
"net/http"
"os"
"runtime"
"sync"
)
const (
fileURL = "http://example.com/large-file.zip" // 要下载的文件URL
// threads = 5 // 并发下载的线程数
outputFile = "output.zip" // 下载完成后的输出文件名
)
func main() {
// CPU数量作为线程数量
numCPU := runtime.NumCPU()
fmt.Println("CPU核心数量:", numCPU)
threads := numCPU
fmt.Println("多线程数量:", threads)
fmt.Println("开始下载文件...")
// 获取文件大小
fileSize, err := getFileSize(fileURL)
if err != nil {
fmt.Println("无法获取文件大小:", err)
return
}
fmt.Println("文件大小:", fileSize, "bytes")
// 计算文件块大小
chunkSize := fileSize / int64(threads)
// 创建等待组,确保所有下载完成后再合并文件
var wg sync.WaitGroup
wg.Add(threads)
// 创建一个通道用于接收下载完成的文件块路径
chunkPaths := make(chan string, threads)
// 启动多个 goroutine 并发下载文件块
for i := 0; i < threads; i++ {
go func(index int) {
defer wg.Done()
start := int64(index) * chunkSize
end := start + chunkSize - 1
// 如果是最后一次线程的结尾设置为文件结尾,确保文件下载的完整性
if index == threads-1 {
end = fileSize - 1
}
fmt.Printf("线程 %d 开始下载:%d-%d\n", index, start, end)
chunkPath, err := downloadChunk(fileURL, start, end)
if err != nil {
fmt.Printf("线程 %d 下载失败:%v\n", index, err)
} else {
fmt.Printf("线程 %d 下载完成:%d-%d\n", index, start, end)
chunkPaths <- chunkPath // 将下载完成的文件块路径发送到通道
}
}(i)
}
// 等待所有下载完成
wg.Wait()
close(chunkPaths) // 关闭通道,表示所有文件块都已下载完成
// 创建一个输出文件
output, err := os.Create(outputFile)
if err != nil {
fmt.Println("无法创建输出文件:", err)
return
}
defer output.Close()
// 合并下载的文件块到输出文件
for chunkPath := range chunkPaths {
fmt.Println("合并文件块:", chunkPath)
err := mergeChunk(chunkPath, output)
if err != nil {
fmt.Printf("合并文件块失败:%v\n", err)
} else {
fmt.Printf("合并文件块成功:%s\n", chunkPath)
}
// 删除临时文件块
err = os.Remove(chunkPath)
if err != nil {
fmt.Printf("删除文件块失败:%v\n", err)
}
}
fmt.Println("文件下载完成。")
}
// 获取文件大小
func getFileSize(url string) (int64, error) {
resp, err := http.Head(url)
if err != nil {
return 0, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return 0, fmt.Errorf("服务器返回错误: %v", resp.Status)
}
return resp.ContentLength, nil
}
// 下载文件块
func downloadChunk(url string, start, end int64) (string, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", err
}
req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", start, end))
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusPartialContent {
return "", fmt.Errorf("服务器不支持分块下载:%v", resp.Status)
}
// 创建一个临时文件用于保存下载的文件块
chunkPath := fmt.Sprintf("chunk_%d_%d.tmp", start, end)
chunkFile, err := os.Create(chunkPath)
if err != nil {
return "", err
}
defer chunkFile.Close()
_, err = io.Copy(chunkFile, resp.Body)
if err != nil {
return "", err
}
return chunkPath, nil
}
// 合并文件块
func mergeChunk(chunkPath string, output *os.File) error {
chunkFile, err := os.Open(chunkPath)
if err != nil {
return err
}
defer chunkFile.Close()
_, err = io.Copy(output, chunkFile)
if err != nil {
return err
}
return nil
}
> go run .\largefile_download_goroutine.go
CPU核心数量: 8
多线程数量: 8
开始下载文件...
文件大小: 28057414 bytes
线程 7 开始下载:24550232-28057413
线程 1 开始下载:3507176-7014351
线程 0 开始下载:0-3507175
线程 4 开始下载:14028704-17535879
线程 3 开始下载:10521528-14028703
线程 5 开始下载:17535880-21043055
线程 6 开始下载:21043056-24550231
线程 2 开始下载:7014352-10521527
线程 0 下载完成:0-3507175
线程 7 下载完成:24550232-28057413
线程 3 下载完成:10521528-14028703
线程 1 下载完成:3507176-7014351
线程 2 下载完成:7014352-10521527
线程 4 下载完成:14028704-17535879
线程 6 下载完成:21043056-24550231
线程 5 下载完成:17535880-21043055
合并文件块: chunk_0_3507175.tmp
合并文件块成功:chunk_0_3507175.tmp
合并文件块: chunk_24550232_28057413.tmp
合并文件块成功:chunk_24550232_28057413.tmp
合并文件块: chunk_10521528_14028703.tmp
合并文件块成功:chunk_10521528_14028703.tmp
合并文件块: chunk_3507176_7014351.tmp
合并文件块成功:chunk_3507176_7014351.tmp
合并文件块: chunk_7014352_10521527.tmp
合并文件块成功:chunk_7014352_10521527.tmp
合并文件块: chunk_14028704_17535879.tmp
合并文件块成功:chunk_14028704_17535879.tmp
合并文件块: chunk_21043056_24550231.tmp
合并文件块成功:chunk_21043056_24550231.tmp
合并文件块: chunk_17535880_21043055.tmp
合并文件块成功:chunk_17535880_21043055.tmp
文件下载完成。