我最近读了很多关于如何编写多线程应用程序是一个巨大的痛苦,并已经足够了解该主题,至少在某种程度上,为什么会这样理解.
我已经读过使用函数式编程技术可以帮助缓解一些痛苦,但我从未见过一个简单的并发功能代码示例.那么,使用线程有哪些替代方案呢?至少,有哪些方法可以将它们抽象出来,这样你就不必考虑锁定之类的事情以及特定库的对象是否是线程安全的.
我知道谷歌的MapReduce应该可以解决问题,但我还没有看到它的简洁解释.
虽然我在下面给出一个具体的例子,但我对一般技术比对解决这个特定问题更感兴趣(使用这个例子来帮助说明其他技术会有所帮助).
当我写一个简单的网络爬虫作为学习练习时,我提出了这个问题.它工作得很好,但速度很慢.大部分瓶颈来自下载页面.它目前是单线程的,因此一次只下载一个页面.因此,如果可以同时下载页面,即使爬虫在单个处理器计算机上运行,也会大大加快速度.我考虑使用线程来解决问题,但他们吓到了我.关于如何在不释放可怕的线程噩梦的情况下为这类问题添加并发性的任何建议?
函数式编程有助于并发的原因并不是因为它避免了使用线程.
相反,功能编程鼓励不变性,并且没有副作用.
这意味着可以将操作扩展到N个线程或进程,而不必担心混淆共享状态.
实际上,在你需要同步它们之前,线程很容易处理.通常,您使用线程池添加任务并等待它们完成.
当线程需要通信并访问共享数据结构时,多线程变得非常复杂.一旦你有两个锁,你就会遇到死锁,这就是多线程变得非常困难的地方.有时,只需几条指令就可能导致锁定代码错误.在这种情况下,您只能看到生产中的错误,在多核计算机上(如果您在单核上开发,发生在我身上),或者它们可能由其他一些硬件或软件触发.单元测试在这里没什么用,测试发现了bug,但你永远不能像"普通"应用程序那样确定.
我将添加一个示例,说明如何使用功能代码安全地使代码并发.
以下是您可能希望并行执行的一些代码,因此您无需等待一个文件完成以开始下载下一个文件:
void DownloadHTMLFiles(Listurls) { foreach(string url in urls) { DownlaodOneFile(url); //download html and save it to a file with a name based on the url - perhaps used for caching. } }
如果您有多个文件,用户可能会花一分钟或更长时间等待所有文件.我们可以像这样在功能上重写这段代码,它基本上完全相同:
urls.ForEach(DownloadOneFile);
请注意,这仍然按顺序运行.然而,它不仅更短,我们在这里获得了重要的优势.由于每次对DownloadOneFile函数的调用都与其他函数完全隔离(出于我们的目的,可用带宽不是问题),您可以非常轻松地将ForEach函数替换为另一个非常类似的函数:一个启动对DownlaodOneFile的每次调用的函数来自线程池的单独线程.
事实证明.Net使用Parallel Extensions可以提供这样的功能.因此,通过使用函数式编程,您可以更改一行代码,并突然有一些并行运行的东西,用于顺序运行.这非常强大.