💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# Pthreads,第 3 部分:并行问题(奖金) > 原文:<https://github.com/angrave/SystemProgramming/wiki/Pthreads%2C-Part-3%3A-Parallel-Problems-%28Bonus%29> ## 概观 下一节讨论 pthreads 碰撞时会发生什么,但如果我们让每个线程完全不同,没有重叠怎么办? 我们发现最大加速并行问题? ## 令人尴尬的并行问题 在过去几年中,对并行算法的研究已经爆发。一个令人尴尬的并行问题是任何需要很少努力转向并行的问题。他们中的很多人都有一些同步概念,但并非总是如此。你已经知道一个可并行化的算法,Merge Sort! ```c void merge_sort(int *arr, size_t len){ if(len > 1){ //Mergesort the left half //Mergesort the right half //Merge the two halves } ``` 通过对线程的新理解,您需要做的就是为左半部分创建一个线程,为右半部分创建一个线程。鉴于您的 CPU 有多个真实核心,您将看到符合 [Amdahl 定律](https://en.wikipedia.org/wiki/Amdahl's_law)的加速。时间复杂度分析也很有趣。并行算法运行在 O(log ^ 3(n))运行时间(因为我们假设我们有很多核心的花式分析。 但在实践中,我们通常会做两处更改。一,一旦数组变得足够小,我们就会抛弃并行 mergesort 算法并执行快速排序或其他算法,这些算法可以在小数组上快速运行(某些东西可以缓存一致性)。我们知道的另一件事是 CPU 没有无限核心。为了解决这个问题,我们通常会保留一个工作人员池。 ## 工人池 我们知道 CPU 的内核数量有限。很多时候,我们启动了许多线程,并在空闲时为他们提供任务。 ## 另一个问题,平行地图 假设我们想要将一个函数应用于整个数组,一次一个元素。 ```c int *map(int (*func)(int), int *arr, size_t len){ int *ret = malloc(len*sizeof(*arr)); for(size_t i = 0; i < len; ++i) ret[i] = func(arr[i]); return ret; } ``` 由于没有任何元素依赖于任何其他元素,您将如何进行并行化?您认为在线程之间拆分工作的最佳方法是什么? ## 调度 分离工作有几种方法。 * 静态调度:将问题分解为固定大小的块(预定义),并让每个线程都在每个块上运行。当每个子问题花费大致相同的时间时,这很有效,因为没有额外的开销。您需要做的就是编写一个循环并将 map 函数赋予每个子数组。 * 动态调度:当一个新问题变得可用时,有一个线程为它服务。当您不知道调度需要多长时间时,这非常有用 * 引导式调度:这是上述各种利益和权衡的混合。您可以从静态计划开始,并根据需要缓慢移动到动态 * 运行时调度:你完全不知道问题需要多长时间。不要自己决定,让程序决定做什么! [来源](https://software.intel.com/en-us/articles/openmp-loop-scheduling),但无需记忆。 ## 几个缺点 由于缓存一致性和调度额外线程之类的东西,你不会马上看到加速。 ## 其他问题 来自[维基百科](https://en.wikipedia.org/wiki/Embarrassingly_parallel) * 一次将 Web 服务器上的静态文件提供给多个用户。 * Mandelbrot 集,Perlin 噪声和类似图像,其中每个点都是独立计算的。 * 渲染计算机图形。在计算机动画中,每个帧可以独立渲染(参见并行渲染)。 * 密码学中的暴力搜索。[8]值得注意的现实世界的例子包括 distributed.net 和加密货币中使用的工作证明系统。 * BLAST 在生物信息学中搜索多个查询(但不是针对单个大型查询)[9] * 大规模面部识别系统,其将数千个任意获取的面部(例如,经由闭路电视的安全或监视视频)与类似大量的先前存储的面部(例如,盗贼画廊或类似的观察列表)进行比较。[10] * 计算机模拟比较许多独立场景,例如气候模型。 * 进化计算元启发式算法,如遗传算法。 * 数值天气预报的集合计算。 * 粒子物理中的事件模拟与重建。 * 行进方块算法 * 筛分步骤的二次筛和数字筛。 * 随机森林机器学习技术的树木生长步骤。 * 离散傅里叶变换,其中每个谐波是独立计算的。