信号量与多线程 2

第十七课

多文件下载

要解决的问题如下:

  • 同时下载多个文件

  • 计算出下载的总字节数

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

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 线程完成之前就执行,而返回错误的数值。我们可以利用信号量来解决这个问题:

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 的计算结果。

参考