# 信号量与多线程 2

#### 多文件下载 <a href="#duo-wen-jian-xia-zai" id="duo-wen-jian-xia-zai"></a>

要解决的问题如下：

* 同时下载多个文件
* 计算出下载的总字节数

我们可能第一反应是这样：

```c
int DownloadSingleFile(const char *server, const char *path);

int DownloadAllFiles(const char *server, 
                     const char *files[],
                     int n) {
    int totalBytes = 0;
    Semaphore lock = 1;
    for (int i = 0; i < n; i++) {
        ThreadNew("...", DownloadHelper, 4
            server, files[i], &totalBytes, lock);
    }
    return totalBytes;
}

void DownloadHelper(const char *server,
                    const char *path,
                    int *numBytesp,
                    Semaphore lock) {
    int bytesDownloaded = DownloadSingleFile(server, path);
    SemaphoreWait(lock); // why not put SW before bytesDownloaded
    (*numBytesp) += bytesDownloaded;
    SemaphoreSignal(lock);                   
}
```

首先这里需要注意 DownloadHelper 中 SemaphoreWait(lock) 语句放置的位置，如果它被放在了 DownloadSingleFile 语句之前，那么并发就无法进行，第二个文件下载必须在第一个文件下载完成之后才能开始，因此在书写相关代码时，要注意将 Thread-safe 的代码放到 critical region 之外，从而得到最高的并发效率。

按照如上做法，可能导致 return totalBytes 在 DonwloadHelper 线程完成之前就执行，而返回错误的数值。我们可以利用信号量来解决这个问题：

```c
int DownloadAllFiles(const char *server, 
                     const char *files[],
                     int n) {
    Semaphore childrenDone = 0;
    int totalBytes = 0;
    Semaphore lock = 1;
    for (int i = 0; i < n; i++) {
        ThreadNew("...", DownloadHelper, 4
            server, files[i], &totalBytes, lock);
    }
    for (int i = 0; i < n; i++) {
        SemaphoreWait(childrenDone);
    }
    return totalBytes;
}

void DownloadHelper(const char *server,
                    const char *path,
                    int *numBytesp,
                    Semaphore lock,
                    Semaphore parentToSignal) {
    int bytesDownloaded = DownloadSingleFile(server, path);
    SemaphoreWait(lock); // why not put SW before bytesDownloaded
    (*numBytesp) += bytesDownloaded;
    SemaphoreSignal(lock);
    SemaphoreSignal(parentToSignal);         
}
```

注意这里 DownloadAllFiles 中的 ThreadNew 和 SemaphoreWait 不应该放在同一个 for 循环中，如果它们在同一个循环中，同样会使得并发失效。

计算 totalBytes 还有另外一种做法，初始化一个 int\[]，大小为总文件数量，这样就可以不需要使用 Semaphore lock，在第二个 for 循环结束后，直接将 int\[] 求和即可得到 totalBytes 的计算结果。

**参考**

* [Stanford-CS107-lecture-17](https://www.youtube.com/watch?v=kF3eSQTFagQ\&t=670s\&index=17\&list=PL9D558D49CA734A02)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://zhenghe.gitbook.io/open-courses/stanford-cs107/di-shi-qi-ke-xin-hao-liang-yu-duo-xian-cheng-2.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
