table {
border-collapse: collapse;
width: 100%;
margin-bottom: 1rem;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
}
tr:nth-child(even) {
background-color: #f9f9f9;
}
pre {
background-color: #f8f8f8;
padding: 15px;
border-radius: 4px;
overflow-x: auto;
}
1、使用大文件对文件复制程序进行计时。使用计时工具来记录程序执行时间,并使用dd或其他方法生成大文件。尽可能获取多种组合的数据并比较结果。性能取决于众多因素,但通过保持其他系统参数不变,就可以对不同实现进行有帮助的比较。建议将结果列在电子表格中以便分析。
可以按照以下步骤操作:
使用 dd 或其他技术生成大文件;
利用计时程序对文件复制程序进行计时;
尽可能获取不同实现组合的数据;
保持其他系统参数相同,对结果进行比较;
将结果列在电子表格中方便分析。
2、编写代码使用一个函数让用户输入并确认密码,以此来测试该函数对键盘回显的抑制功能。
需要编写代码实现用该函数让用户输入并确认密码,观察是否有键盘回显来测试其抑制功能。具体而言,要调用该函数提示用户输入密码,再提示用户确认密码,在这个过程中查看屏幕上是否显示用户输入的字符,如果不显示则说明该函数抑制键盘回显功能有效。
3、使用你能使用的计算机上的测试程序来研究文件复制和转换的性能。此外,如果可能的话,研究使用网络文件系统、存储区域网络(SAN)等进行顺序文件访问时各种存储架构的影响。
研究任务描述
该问题是一个研究任务,要求用可使用的计算机上的测试程序研究文件复制和转换的性能。若条件允许,还要研究网络文件系统、存储区域网络等在顺序文件访问时不同存储架构的影响。
4、使用特定函数确定不同Windows版本如何稀疏地分配文件空间。步骤如下:创建一个新文件,将文件指针设置为一个较大的值,设置文件大小,并使用该函数调查可用空间。同样的Windows函数还可用于确定磁盘如何配置为扇区和簇。判断新分配的文件空间是否已初始化。示例文件中提供的[具体函数]是解决方案。最后比较NT5和NT6在上述操作中的结果,并研究如何使文件成为稀疏文件。
可按以下步骤操作:
创建新文件
将文件指针设为较大值
设置文件大小
用对应函数调查可用空间,以确定不同 Windows 版本分配文件空间的稀疏情况
用同一函数确定磁盘扇区和簇的配置
判断新分配文件空间是否初始化
– 示例文件中的
[具体函数]
是解决方案
比较 NT5 和 NT6 的结果
还可研究使文件成为稀疏文件的方法
5、编写一个文件复制命令程序,使其能够将文件复制到目标目录。进一步扩展该程序的功能,通过添加特定选项,支持递归复制(即若源路径是目录,将该目录及其所有子目录和文件都复制到目标目录)以及保留复制文件的修改时间。实现递归复制选项时需要创建新目录。
功能增强需求
需要对文件复制命令进行增强,以实现将文件复制到目标目录的功能。同时,通过添加特定选项,支持递归复制和保留复制文件的修改时间。在实现递归复制时,要创建新目录。
6、增强一个程序,使新文件时间可以在命令行中指定。UNIX命令允许时间戳(可选)出现在常规选项之后、文件名之前。时间格式为[ ,其中大写 表示月份, 表示分钟。请描述如何实现该功能。
在程序中添加功能,让用户能在命令行指定新文件时间,时间格式遵循[ ,大写 为月份, 为分钟。
7、程序是为处理大型NTFS文件系统而编写的。若有足够可用磁盘空间,用一个长度大于4GB的巨大文件来测试这个程序,验证64位算术运算是否正确。不建议在未经网络管理员许可的情况下在网络驱动器上进行此练习。完成后要删除测试文件。请简述测试该程序的要点。
测试该程序的要点包括:
使用长度大于4GB的巨大文件进行测试
验证64位算术运算
注意未经网络管理员许可不得在网络驱动器上进行测试
完成后删除测试文件
8、不同版本的 Windows 在堆的开销内存方面存在显著差异。设计并进行一项实验,以测量每个系统在单个堆中能提供多少个固定大小的块。使用结构化异常处理(SEH)来检测何时所有块都已分配,会使程序更易于实现。
可使用示例文件中的测试程序来开展该实验。利用 SEH 检测所有块分配完毕的情况,从而测量每个系统在单个堆中能提供的固定大小块的数量。
9、创建一个程序,该程序分配一个足够大的内存缓冲区来容纳文件,并将文件读入该缓冲区,不进行内存映射。再与一个使用内存映射的程序进行性能比较。
可以按如下步骤操作:
首先编写程序,分配内存并将文件完整读入内存。
在内存中排序后写入。
这种不使用映射的解决方案效果与使用内存映射的程序相当,且通常速度更快。
要比较二者性能,可通过计时的方式,分别记录两个程序完成文件处理的时间,时间短的性能更佳。
此外,若在32位计算机上运行,内存映射无法处理大于约1.5GB的文件,而不使用内存映射的新程序则不受此限制。
10、如果编译器不支持微软C特有的指针特性,或者仅为练习,如何用宏、数组或其他机制重新实现程序以生成基指针值?
需用宏、数组或其他机制重新实现程序来生成基指针值,以适配不支持微软C特有指针的编译器或用于练习。
11、修改一个多进程程序,该程序原本会等待所有进程完成后再列出结果,且无法确定进程实际完成的顺序。要求修改程序,使其能够确定进程的终止顺序。提示:可以修改调用,使每次单个进程终止后返回;也可以按进程终止时间排序。
可通过两种方式修改程序以确定进程终止顺序:
修改调用,让其在每个进程终止后就返回;
按进程终止时间进行排序。
12、确定在不同多处理器系统上,或者文件位于独立或网络驱动器上时,并行执行与顺序执行相比有哪些性能优势
运行6 – 1显示,并行执行比四次顺序执行快四倍多(实时时间为15秒,而顺序执行是77秒),多进程即使有进程创建开销,仍获得了比预期更多的性能提升。在多处理器环境中,程序中的进程及其主线程几乎完全相互独立运行,Windows调度程序可让进程线程在多处理器计算机的不同处理器上并发运行,能大幅缩短程序执行的耗时,且无需显式的程序操作即可实现性能提升。不过,由于开销成本和顺序输出结果的需要,性能提升并非与处理器数量成线性关系。
13、作业管理函数存在一个难以修复的缺陷。假设一个作业被终止,并且在作业管理文件中删除该进程ID之前,执行程序重新使用了该进程ID,这可能会对现在指向完全不同进程的进程ID产生混淆。请说明修复该缺陷的方法,并对比UNIX和Windows在进程ID复用机制上的差异,同时指出Windows程序在进程ID复用方面应注意的问题。
作业管理函数缺陷及修复方案
作业管理函数存在进程ID复用导致混淆的缺陷,可通过以下方式修复:
创建辅助进程持有复制句柄
在作业管理文件中包含进程启动时间
不同系统进程ID机制对比
UNIX系统
通过递增一个计数器来获取新的进程ID
只有在32位计数器溢出后,ID才会重复
Windows系统
进程ID会被快速重复使用
因此Windows程序不能忽视ID复用问题
14、增强程序,使命令能够包含每个作业正在使用的句柄数量统计。
可通过相关编程手段实现命令包含每个作业正在使用的句柄数量统计。例如在 Windows 系统下,使用 Windows API 函数来获取句柄信息。可使用
CreateToolhelp32Snapshot
函数获取系统进程和线程的快照,再结合
Process32First
和
Process32Next
遍历每个进程。
对于每个进程可以使用
OpenProcess
打开进程句柄,然后使用
NtQueryObject
等函数来获取该进程使用的句柄数量等信息,并将这些信息整合到命令中。
不同编程语言有不同的实现细节,以 Python 为例,可以使用
psutil
库来获取进程信息和句柄数量:
import psutil
for proc in psutil.process_iter(['name', 'num_handles']):
try:
process_info = proc.info
print(f"进程名: {process_info['name']}, 句柄数量: {process_info['num_handles']}")
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
pass
15、示例文件中包含一个多线程单词计数程序,其结构与另一个程序类似。同时还包含一个有缺陷的版本。在不参考正确解决方案的情况下,分析并修复该有缺陷版本中的缺陷,包括任何语法错误。此外,创建能说明这些缺陷的测试用例,并进行与另一个程序类似的性能实验。如果你使用 Cygwin(开源的 UNIX/Linux 命令和 shell),比较 Cygwin 的该程序与该多线程单词计数程序的性能,特别是在多处理器系统上。
本题需要进行一系列操作:
首先,分析有缺陷版本程序中的缺陷并修复语法错误。
接着,创建测试用例来展示这些缺陷。
然后,开展与另一个程序类似的性能实验。
最后,若使用 Cygwin,比较 Cygwin 中该程序和多线程单词计数程序在多处理器系统上的性能。
16、编写一个程序,增强其逻辑,使得当命令行指定的线程数为零时,程序自动确定主机计算机的处理器数量。接着,将线程数分别设置为处理器数量的 1 倍、2 倍、4 倍等不同倍数,然后确定不同线程数设置对程序性能的影响。请给出该程序的代码示例及性能测试的方法。
在程序中添加逻辑,当命令行指定线程数为零时,使用相应函数确定主机处理器数量;然后设置线程数为处理器数量的不同倍数;最后通过性能测试工具或自定义性能测量逻辑来确定不同线程数对程序性能的影响。以下是一个 Python 示例代码:
import multiprocessing
import time
# 模拟一个耗时的任务
def task():
result = 0
for i in range(1000000):
result += i
return result
# 运行任务并测量时间
def run_tasks(num_threads):
start_time = time.time()
pool = multiprocessing.Pool(processes=num_threads)
results = [pool.apply_async(task) for _ in range(num_threads)]
pool.close()
pool.join()
end_time = time.time()
return end_time - start_time
if __name__ == '__main__':
import sys
if len(sys.argv) > 1:
try:
num_threads = int(sys.argv[1])
except ValueError:
num_threads = 0
else:
num_threads = 0
if num_threads == 0:
num_processors = multiprocessing.cpu_count()
else:
num_processors = num_threads
multiples = [1, 2, 4]
for multiple in multiples:
current_threads = num_processors * multiple
elapsed_time = run_tasks(current_threads)
print(f'线程数: {current_threads}, 耗时: {elapsed_time} 秒')
性能测试方法
:通过
time
模块记录任务开始和结束的时间,计算时间差得到任务执行的耗时,以此来评估不同线程数下程序的性能。
17、创建所有线程时将其置于挂起状态以避免竞态条件。修改程序,使其以相反顺序创建线程并让线程处于运行状态。是否还存在竞态条件?将其性能与原始版本进行比较。
由于没有关于原程序的具体信息,无法确定是否还存在竞态条件以及与原始版本的性能比较结果。但一般来说,以运行状态创建线程且不做额外同步处理,很可能会存在竞态条件,因为线程是异步执行的,各线程执行顺序不确定。
性能方面,若原程序因线程挂起和恢复操作有额外开销,修改后可能性能有所提升;若原程序的挂起机制避免了大量的竞态问题,修改后可能因频繁处理竞态问题导致性能下降。
18、创建一个工作线程池,但要控制工作线程的运行速率,使得在任意1秒间隔内只允许一个线程运行。修改程序,使该间隔内可以运行两个线程,但线程的整体运行速率限制为每秒一个。提示:工作线程应等待一个事件,并且一个控制线程应每秒对该事件发出信号。
可使用手动重置事件,控制线程每秒调用
SetEvent
发出信号。
若要在 1 秒内运行两个线程但整体速率为每秒一个,可设置计数器,控制线程根据计数器状态发出信号。
19、在一个涉及多种变体的实验中,工作函数包含延迟时间。当修改工作函数中的延迟时间时,会对这些变体产生影响。请分析增加延迟时间会对所有变体的总耗时以及锁模型的相对影响分别产生什么变化。
增加延迟时间会增加所有变体的总耗时,同时锁模型的相对影响可能会减小。
20、还可以使用信号量节流阀来限制运行线程的数量。在单处理器和多处理器系统上对该数量进行实验。如果有 NT4 系统,将结果与 NT5 和 NT6 进行比较。
可以按照以下步骤完成操作:
使用信号量节流阀限制运行线程数量
在单处理器和多处理器系统中,实现方式如下:
– 老板线程创建一个最大值较小的信号量,代表活跃线程的最大数量。
– 初始计数也设为该最大值,此数值可作为参数,通过实验调整到最佳值。
– 每个工作线程在进入关键代码段前等待信号量。
– 离开关键代码段后立即释放信号量。
在 NT4 系统上进行实验
– 若需支持 NT4 系统,则在该系统上重复上述实验。
– 记录不同线程数量下的性能数据,如运行时间、资源利用率等。
系统间性能对比分析
– 将 NT4 系统的实验结果与 NT5 和 NT6 系统的结果进行对比。
– 分析不同系统在使用信号量节流阀时的性能差异。
– 由于 NT5 和 NT6 改进了同步性能,可能在多线程处理上比 NT4 更具优势。
21、通过修改程序,将处理器亲和性作为一种可能的性能增强技术加以应用,应该怎么做?
以下是对给定文本内容进行 Markdown 格式调整后的结果:
可以从以下几个方面利用处理器亲和性修改程序以增强性能:
为少量线程专门分配一个处理器,并排除其他线程使用该处理器。
不过前提是要控制所有运行的应用程序,且即使如此,Windows 仍可能在该处理器上调度自身的线程。
将一组线程分配给共享 L2 缓存的处理器对,以减少内存屏障造成的延迟。
将竞争单一资源的工作线程分配到单个处理器上。
此外,还可通过设置系统、进程和线程亲和性掩码来实现处理器亲和性。每个进程有自己的进程亲和性掩码,系统有系统亲和性掩码,每个线程有线程亲和性掩码,且线程亲和性掩码必须是进程亲和性掩码的子集。
22、修改一个程序,使其可以有多个消费者,并使用条件变量模型。哪种事件类型合适?
应使用手动重置事件(广播模型),这样当队列非空或非满时,多个线程能得到通知。
23、程序使用广播条件变量(CV)模型来指示队列是不空还是不满。信号CV模型是否可行?信号模型在某些方面是否更可取?
信号CV模型可行。在某些情况下信号模型更可取,例如:
信号模型能随线程数量扩展,而广播模型不能,尤其是线程数达到32和64时;
广播模型会导致大量系统CPU时间消耗,而信号模型在这方面表现更好。
24、研究使用临界区(CS)而非互斥锁(mutex)时,程序的性能表现。
在信号模型中,临界区(CS)与条件变量相比表现良好,尽管 CS 实现需要超时设置。在同步性能影响方面,CS 版本比无同步(NS)和互锁函数(IN)版本明显慢,比 IN 慢 2 倍或更多,而互斥锁(MX)版本执行时间可能是 CS 的 2 到 30 倍。
在 NT5 之前,当线程数超过 4 时,CS 性能并非总是随线程数量线性提升,NT5 对此有显著改进。此外,临界区(CS)的锁定和解锁操作效率较高,因为 CS 测试在用户空间进行,无需像互斥锁那样进行内核系统调用,解锁完全在用户空间完成。
25、阅读施密特和皮亚拉利所著的《在 Win32 中实现 POSIX 条件变量的策略》。将他们关于公平性、正确性、序列化等方面的分析应用到一个条件变量模型(该论文中称为‘惯用法’)。注意,此条件变量模型并非直接模拟条件变量,而是解决了模拟正常条件变量使用这一相对简单的问题,而施密特和皮亚拉利模拟的是在任意上下文中使用的条件变量。
任务要求
阅读指定文章。
分析文章中关于
公平性
、
正确性
、
序列化
等内容。
将上述分析应用到
条件变量模型
中。
注意
条件变量模型
与文章在模拟条件变量方面的
差异
。
26、修改使用条件变量实现消息队列管理函数的代码,使其使用 SRW(轻量级读写)锁。对修改后的代码进行测试,并将性能与原始实现进行比较。进一步修改实现以使用线程池。
需对使用条件变量实现消息队列管理函数的代码进行修改
将其改为使用 SRW 锁
进行测试并与原实现比较性能
最后再修改代码以使用线程池
27、启动服务器和多个客户端,每个客户端使用“分离”选项创建。最后,通过命令发送控制台控制事件来关闭服务器。你能对关闭逻辑提出一些改进建议,使连接的服务器线程在阻塞等待客户端请求时可以测试关闭标志吗?提示:创建一个类似于连接线程的读取线程。
可以创建一个类似于连接线程的
读取线程
。该读取线程可以在等待客户端请求时,周期性地检查
全局关闭标志
。这样,即使服务器线程在阻塞等待客户端请求,也能通过读取线程来测试关闭标志,从而实现更合理的关闭逻辑。
28、在不同系统上运行客户端和服务器,以确认网络操作是否正确。编写一个程序,使其在命名管道中包含服务器机器名。此外,修改程序中当前硬编码的邮件槽名称,使其从应用服务器的邮件槽响应中获取该名称。
需在不同系统运行客户端和服务器来验证网络操作。编写程序,在命名管道里加入服务器机器名,同时从应用服务器的邮件槽响应获取邮件槽名称并修改程序中的对应部分。
29、该程序被设计为作为服务器无限期运行,允许客户端连接、获取服务和断开连接。当客户端断开连接时,服务器释放所有相关资源(如内存、文件句柄和线程句柄)非常重要。任何残留的资源泄漏最终都会耗尽计算机资源,导致服务器故障,并且在故障发生前可能会出现显著的性能下降。仔细检查该程序,确保没有资源泄漏,如果发现任何泄漏,请修复它们。(另外,请使用前言中的电子邮件地址通知作者。)注意:资源泄漏是许多生产系统中常见且严重的缺陷。如果一项‘行业级’的质量保证工作没有解决这个问题,那么它就是不完整的。
需要仔细检查相关程序,排查是否存在资源泄漏情况,若发现资源泄漏(如客户端断开连接后未释放内存、文件句柄和线程句柄等)则进行修复,并通过前言中的电子邮件地址通知作者。
30、扩展练习:同步对象可以同步同一机器上不同进程中的线程,但无法同步运行在不同机器上进程中的线程。如何使用命名管道和邮件槽创建模拟的互斥量、事件和信号量来克服这一限制?
模拟同步机制的实现方法
本题可按以下步骤创建模拟的互斥量、事件和信号量:
1. 模拟互斥量
使用命名管道或邮件槽实现互斥访问。当一个线程想要获取互斥量时,它尝试向管道或邮件槽写入特定标识,若写入成功则获得互斥量;其他线程尝试写入时会失败,表明互斥量已被占用。释放互斥量时,将特定标识从管道或邮件槽中移除。
2. 模拟事件
通过命名管道或邮件槽传递信号来模拟事件。一个线程可以向管道或邮件槽发送“事件触发”的消息,其他等待该事件的线程通过读取管道或邮件槽来检测事件是否触发。
3. 模拟信号量
使用命名管道或邮件槽来记录可用资源的数量。线程在请求资源时,先检查管道或邮件槽中的资源数量,若有可用资源则减少相应数量;释放资源时,增加资源数量。
通过这些方法,可以利用命名管道和邮件槽在不同机器上的进程间实现类似互斥量、事件和信号量的同步功能。
31、进行实验以确定进程内服务器是否比进程外服务器更快。可以使用一个包含可执行程序以及DLL函数的字数统计示例来开展实验。
可使用包含可执行程序和DLL函数的字数统计示例开展实验,通过此实验来判断进程内服务器是否比进程外服务器速度更快。
32、可支持的客户端数量受服务器线程参数数组的限制。修改程序,使其不存在这样的限制。你需要创建一个数据结构,该结构允许添加和删除线程参数,并且能够扫描该结构以查找已终止的服务器线程。另一种可以说更简单的方法是,让每个服务器线程管理自己的状态,除了要求服务器线程关闭并等待它们完成之外,主线程不参与其中。
解决客户端数量受服务器线程参数数组限制的方案
要解决可支持客户端数量受服务器线程参数数组限制的问题,有两种方法:
方法一
创建一个能添加和删除线程参数的数据结构,同时可扫描该结构查找已终止的服务器线程。
方法二
让每个服务器线程自行管理状态,主线程仅负责要求服务器线程关闭并等待完成。
33、改进程序,使其使用 SSL 进行安全的客户端/服务器通信。
需先了解 SSL 相关知识,再对程序进行修改,以利用 SSL 实现安全的客户端/服务器通信。
34、在查询服务状态时,仅打印出数字。对其进行扩展,使状态以更易读的形式呈现。
可通过创建一个状态码与状态描述的映射表,当查询服务状态获取到数字状态码后,从映射表中查找对应的描述信息并输出,以此将数字状态转换为更易读的文本形式。
35、在练习文件中对某个程序进行测试,并修改该程序使其使用事件日志记录。
可先在练习文件中找到要测试的程序,对该程序进行修改,添加使用事件日志记录的代码。
一般在不同编程语言中都有相应的事件日志记录库或方法,例如:
在 Python 中可使用
logging
模块
在 Java 中可使用
java.util.logging
包
将关键事件信息记录到日志中。
36、重写程序,使就绪的工作线程数量受信号量限制。用大型线程池进行实验,以确定这种替代方法的有效性。
要实现让就绪的工作线程数量受信号量限制,可按以下步骤操作:
主线程创建一个最大计数值较小的信号量,代表能同时活跃的最大线程数,可设为处理器数量,初始计数值也设为最大值,该值可作为参数,通过实验调整到最佳。
每个工作线程在进入临界代码区前等待信号量,信号量等待操作可紧接在互斥锁或其他等待操作之前。
工作线程离开临界代码区后,立即释放信号量(释放计数值为 1)。
若信号量最大值为 1,互斥锁则多余。
随着线程执行被序列化,只有少数线程在互斥锁或临界区上等待,整体临界区或互斥锁的竞争会减少。
对于实验部分,可创建一个大型线程池,通过调整信号量的最大计数值,观察系统性能指标,如 CPU 使用率、线程响应时间、吞吐量等,来确定这种方法的有效性。若在不同信号量最大计数值下,系统性能有明显差异,且能找到一个使性能最优的值,就说明该方法有效。
37、修改程序,使得客户端数量没有限制。使用某个值作为相关参数的值。需要用动态分配的结构替换结构数组。
可通过动态内存分配(如在C语言中使用
malloc
函数)来创建所需的数据结构,以实现客户端数量无限制的功能,并将原有的结构数组替换为动态分配的结构。