1 读写循环队列问题
2 哲学家问题
- busy waiting
忙等待 - critical region
临界区域 - concurrency
critical region 具有 “concurrency” 并发性 - resource contention
临界区域的代码会发生“resource contention”资源冲突(争夺资源)
3 FTP问题
- servers
服务器:载有文件的计算机 - path
路径:web目录
3.1 经理给员工分配任务
本项目将体会信号量的两个用途:
- 1、竞争:作用域critical region用于保护全局资源
- 2、同步:监控子线程完成动作(thread-to-thread communication 的一种)
任务描述:
经理需要去FTP上下载一个文件夹(文件数组)的内容!
他找了n个员工,让每个员工从目标文件夹(文件数组)中下载1个文件!这n个员工同时下载!
完事之后,返回这n个员工总共下载的字节数 totalBytes
3.1.1 经理偷懒了
经理分配完任务后,就回家睡觉了!没有等待员工下载完成!结果返回的总字节数 totalBytes 很可能是0!!!
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,
server, files[i],
&totalBytes,
lock);
}
// 这里有问题的,经理分配完任务之后,直接就返回totalBytes了,此时,可能员工还没下载完成!
return totalBytes;
}
// ThreadNew接收不到返回值,所以需要用函数入参的指针传出参数!
void DownloadHealper(const char *server,
const char *path,
int *pNumBytes,
Semaphore lock)
{
int bytesDownloaded = DownloadSingleFile(server, path);
SemaphoreWait(lock); // critical region begin >>>
(*pNumBytes) += bytesDownloaded;
SemaphoreSignal(lock); // <<< critical region end
}
3.1.2 让经理等待员工下载完成
int DownloadAllFiles(const char *server,
const char *files[],
int n)
{
int totalBytes = 0;
Semaphore lock = 1;
Semaphore childrenDone = 0; // 用于监控孩儿们下载完文件没有!!
for(int i = 0; i < n; i++) {
ThreadNew(___, DownloadHelper,
server, files[i],
&totalBytes,
lock,
childrenDone);
}
// 这里有问题的,经理分配完任务之后,直接就返回totalBytes了,此时,可能员工还没下载完成!
// 把经理拉回来监控孩儿们!!!
for(int i = 0; i < n; i++) {
// -- n 次,共有n个孩儿干活,让每个孩儿完成工作后,做1次 ++ 操作
SemaphoreWait(childrenDown);
}
return totalBytes;
}
// ThreadNew接收不到返回值,所以需要用函数入参的指针传出参数!
void DownloadHealper(const char *server,
const char *path,
int *pNumBytes,
Semaphore lock,
Semaphore parentToSignal)
{
int bytesDownloaded = DownloadSingleFile(server, path);
SemaphoreWait(lock); // critical region begin >>>
(*pNumBytes) += bytesDownloaded;
SemaphoreSignal(lock); // <<< critical region end
SemaphoreSignal(parentToSignal); // 下载完成后给信号量做 ++操作
}
问题:在DownloadHealper()中,如果将SemaphoreWait(lock); 提前放在DownloadSingleFile()会怎么样?改动代码如下:
// ThreadNew接收不到返回值,所以需要用函数入参的指针传出参数!
void DownloadHealper(const char *server,
const char *path,
int *pNumBytes,
Semaphore lock,
Semaphore parentToSignal)
{
// 向前扩大 critical region
SemaphoreWait(lock); // critical region begin >>>
int bytesDownloaded = DownloadSingleFile(server, path);
(*pNumBytes) += bytesDownloaded;
SemaphoreSignal(lock); // <<< critical region end
SemaphoreSignal(parentToSignal); // 下载完成后给信号量做 ++操作
}
答:
这样的话,孩儿们的并行下载变成了孩儿们逐个下载文件,并行下载失去了作用!会消耗大量时间!
3.1.3 避开全局资源争夺的方法
避免孩子们争夺资源的办法:就是提前给每个孩子均等分配资源,"1人1个" 就不抢了将下载的总字节数 totalBytes 定义成 n 个元素的数组,为每个孩儿开辟一个存储空间,存放孩儿们自个下载的字节数,在经理那里对totalBytes数组中的元素加和,得到总字节数!
问题:如果孩儿太多,那么 downloadBytes[n] 这个数组肯定很大!太浪费空间了!
int DownloadAllFiles(const char *server,
const char *files[],
int n)
{
int downloadBytes[n];
int totalBytes = 0;
Semaphore lock = 1;
Semaphore childrenDone = 0; // 用于监控孩儿们下载完文件没有!!
for(int i = 0; i < n; i++) {
ThreadNew(___, DownloadHelper,
server, files[i],
downloadBytes,
i,
lock,
childrenDone);
}
// 这里有问题的,经理分配完任务之后,直接就返回downloadBytes了,此时,可能员工还没下载完成!
// 把经理拉回来监控孩儿们!!!
for(int i = 0; i < n; i++) {
// -- n 次,共有n个孩儿干活,让每个孩儿完成工作后,做1次 ++ 操作
SemaphoreWait(childrenDown);
}
// 由经理进行孩儿们的下载总字节数计算
for(int i = 0; i < n; i++) {
totalBytes += downloadBytes[i];
}
return totalBytes;
}
// ThreadNew接收不到返回值,所以需要用函数入参的指针传出参数!
void DownloadHealper(const char *server,
const char *path,
int *aNumBytes,
int myId,
Semaphore lock,
Semaphore parentToSignal)
{
int bytesDownloaded = DownloadSingleFile(server, path);
// SemaphoreWait(lock); // critical region begin >>>
aNumBytes[myId] = bytesDownloaded; // 每个孩儿有自己独立的空间存放下载字节数
// SemaphoreSignal(lock); // <<< critical region end
SemaphoreSignal(parentToSignal); // 下载完成后给信号量做 ++操作
}
4 冰激凌商店模拟问题
思考问题的流程:
步骤 | 关键词 | 说明 |
---|---|---|
1 | 定义问题 | 把问题描述清楚 |
2 | 实现方案 | 头脑风暴,将问题进一步细化,思考每一部分的解决方案 |
3 | 具体实现 |
- 经理(数量:1个)
负责检查店员制作的冰激凌是否合格,经理办公室每次只允许1个店员进入,即:经理每次只能检查1个店员制作的冰激凌; - 店员(数量:10~40个)
负责制作可口的冰激凌,每个店员同一时间只能制作1个冰激凌; - 顾客(数量:10个)
去找店员订购冰激凌(1~4个限制),等拿到冰激凌后找收银员结账,然后离开商店; - 收银员(数量:1个)
需要应对10个顾客,每次只能为1个顾客结账!要有“先来后到”哦,为先到的顾客先提供服务!
5 函数式编程Scheme
5.1 Scheme 概述
古老的几何学:丈量土地:对时间和空间进行形式化表述,归纳出一套讨论数学真理的形式化方法!
这直接造就了“公理化方法(axiomatic method)”和各种现代化数学的产生!
计算科学研究的:对“计算过程”形式化表述!
控制复杂度(complexity)的技术
- 黑盒(black-box)抽象
目的① 封装。将处理过程放进1个黑盒之中隐匿细节,从而去构建更大的黑盒!
目的② 泛化。使得表达方式具有普遍性,可以应对多种不同的对象! - 约定接口(interface)
整型加法、字符串加法、多项式加法、电信号加法虽然这些都是加法,但是实现是不一样的!如何增加“普遍性”,让加法对这些不同的对象都适用呢?这就要用到接口(回调函数)! - 元语言(metalinguistic)抽象
学习一门语言:
-
基本元素:
primitive data type 基本数据类型;
operator 操作符; -
组合的方法:
combination 组合式 = expression 表达式
prefix notation 前缀表示法(操作符在操作数的左侧) -
抽象的方法
5.2 VSCode + ChezScheme开发环境搭建
5.2.1 下载并安装ChezScheme
ChezScheme下载网址:
https://cisco.github.io/ChezScheme/
5.2.2 环境变量配置
5.2.2.1 ChezScheme的命令路径:
64位机:
32位机:
5.2.2.2 添加ChezScheme命令路径到用户路径
5.2.2.3 验证ChezScheme是否安装成功
5.2.3 VSCode 配置
5.2.3.1 VSCode 安装 Scheme 语法高亮插件
5.2.3.2 VSCode 安装 Code Runner 插件
5.2.3.3 用 json 设置 VSCode
配置代码 settings.json
{
"code-runner.runInTerminal": true,
"code-runner.executorMapByFileExtension": {
".ss": "scheme"
}
}
出现了错误 csi !
scheme的命令行参数是 scheme,不是csi -script!
5.2.3.4 成功运行 scheme 程序
5.3 scheme 基本语法
基本数据类型 + 操作符 → 组合式
car 和 cdr
cons 合成新的列表
注意:Scheme或者任何Lisp语言的列表都是可以heterogeneous(异构的)。
append 拼接list
display 显示字串
5.4 scheme 抽象方法
5.4.1 定义变量
5.4.2 定义函数
5.4.3 递归