学习笔记7
关于知识点
知识点归纳
第四章 并发编程
- 4.1 并行计算导论
并行计算基础:了解并行计算的基本概念,包括并行性、并行计算模型、并行算法和并行体系结构等;
并行计算模型:学习常见的并行计算模型,如SIMD(单指令多数据)、MIMD(多指令多数据)、向量处理机、阵列处理机和多处理器系统等;
并行算法设计:掌握并行算法的设计方法和技巧,包括分解法、平衡法、局部法、迭代法等,以及如何针对不同的并行计算模型设计相应的并行算法;
并行计算实例:通过具体的实例,如矩阵乘法、排序、搜索、图形处理等,了解并行计算在实际问题中的应用和并行算法的实现;
并行计算性能评估:学习并行计算性能的评估方法和指标,如吞吐量、加速比、能效比等,以及如何优化并行计算性能;
并行计算编程模型:了解常见的并行计算编程模型,如MPI(消息传递接口)、PVM(并行虚拟机)、OpenMP、TBB(线程块)和Cilk等,以及它们的特点和应用场景;
并行计算应用领域:了解并行计算在各个领域中的应用,如科学计算、数据处理、人工智能、图形渲染等。通过学习并行计算导论的知识点,可以建立起对并行计算的基本认识,为进一步学习和研究并行计算相关领域打下基础。- 4.1.1 顺序算法与并行算法
- 4.1.2 并行性与并发性
并行性和并发性虽然有相似之处,但在含义和运用上还存在一些不同之处:
相同点:
- 4.1.1 顺序算法与并行算法
- 同时性:无论是并行性还是并发性,都强调了多个任务同时执行的概念。
- 多任务处理:并行性和并发性都涉及到多任务处理,可以充分利用计算机系统的资源。
- 提高效率:无论是并行性还是并发性,都旨在提高计算机系统的执行效率和性能。
不同点: - 定义和目标:并行性强调同时执行多个任务,将多个任务分配给多个处理器或计算节点,以提高计算速度和处理效率;而并发性强调任务的时间重叠执行和调度,以提高系统的容错性、资源利用率和响应时间。
- 关注点:并行性更关注任务之间的通信和同步,因为任务之间可能需要共享数据和协调工作;而并发性更关注任务之间的调度和资源共享,任务之间可以独立运行,互不干扰。
- 执行环境:并行性通常在多处理器系统或分布式系统中实现,可以同时执行多个任务;而并发性可以在单个处理器上实现,多个任务按照一定的调度策略轮流执行。
总之,并行性和并发性都涉及到多任务处理,但在定义、目标和关注点上有所区别。并行性更关注任务的同时执行,通过分配多个处理器或计算节点来提高计算速度和处理效率;而并发性更关注任务的时间重叠执行和调度,通过资源共享和调度进程等实现系统的多任务处理。
- 4.2 线程
- 4.2.1 线程的原理
线程是计算机程序中能够执行的最小单位,它由一组指令序列和相关的数据组成。线程的原理主要包括线程的调度和上下文切换。
线程调度:线程调度器(也称为调度程序)负责决定哪个线程可以在处理器上执行。线程调度器根据一定的调度策略,如优先级调度、时间片轮转调度等,为线程分配执行的时间片,并控制线程在处理器上的执行。线程调度可以采用抢占式调度或合作式调度。
上下文切换:当线程调度器决定切换到某个线程时,将会进行上下文切换。上下文切换是指保存当前线程的执行环境(包括寄存器、程序计数器等)并加载下一个线程的执行环境。这样可以保证线程的状态得到正确恢复,从而实现线程的切换和执行。
线程的原理涉及到操作系统的参与,操作系统提供线程调度器和上下文切换的机制来支持多线程的执行。通过合理的调度策略和高效的上下文切换,可以实现多线程的并发执行,提高计算机系统的性能和吞吐量。 - 4.2.2 线程的优点
- 4.2.1 线程的原理
- 资源共享:线程可以共享同一进程的资源,包括内存空间、文件句柄和其他系统资源。这使得线程之间可以方便地共享数据、通信和协作,提高了程序的灵活性和效率;
- 资源高效利用:相比于进程,线程的创建和销毁所需的系统资源开销更小,且上下文切换的开销也较小。因此,在同时执行多个任务时,使用线程可以更高效地利用计算机资源;
- 响应性增强:通过将程序中的某些任务放在单独的线程中执行,可以提高程序的响应性。这样,当一个线程被阻塞时,其他线程仍然可以继续执行,从而保持程序的流畅性和用户体验;
- 并发性与并行性:线程可以通过并行执行多个任务来提高程序的并发性和并行性。在多核或多处理器系统中,不同线程可以同时在不同的核或处理器上执行,加快程序的运行速度;
- 简化编程模型:相对于进程,线程的创建、销毁和切换更加简单。多线程编程模型可以方便地实现任务的分配和协作,减少了编程的复杂度;
总之,线程具有资源共享、资源高效利用、响应性增强、并发性与并行性以及简化编程模型等优点,使得线程在多任务处理和多核/多处理器系统中得到广泛应用。- 4.2.3 线程的缺点
- 资源竞争:多个线程共享同一资源时,可能会出现竞争条件(如互斥访问共享变量),导致数据不一致或产生死锁等问题。需要使用同步机制(如锁、信号量)来解决资源竞争问题,增加了编程的复杂性;
- 死锁:在多线程环境下,不正确的同步操作可能导致死锁,即线程相互等待对方释放资源而无法继续执行。死锁会导致程序无法正常执行或陷入无限等待的状态,对系统的稳定性和性能造成影响;
- 调试困难:由于多线程程序的执行是并发的,各个线程之间的相互影响和交互较为复杂,因此调试多线程程序通常比单线程程序更加困难。找出并发问题、定位线程错误和排查竞争条件等需要更多的分析和工具支持;
- 上下文切换开销:在多线程环境下,线程的调度和上下文切换会带来一定的开销,包括保存和恢复线程的执行环境、更新线程的状态等。当线程数量较多时,频繁的上下文切换可能占用大量的计算机资源,降低系统的性能;
- 容易引发并发 bug:并发编程难度较高,设计和实现正确的并发算法和同步机制需要考虑多个线程间的交互和状态变化,容易出现隐藏的并发 bug。例如,出现数据竞争、死锁、饥饿等问题,需要经过仔细的调试和测试;
总之,线程的缺点包括资源竞争、死锁问题、调试困难、上下文切换开销和容易引发并发 bug 等。在设计和实现多线程程序时,需要仔细考虑这些问题,使用合适的并发控制机制和编程范式,以提高程序的正确性和性能。
- 4.3 线程操作
线程操作涉及到多线程编程中对线程的创建、销毁、同步和通信等操作。
- 线程创建:线程的创建是通过调用特定的线程创建函数或方法来实现的。通常,需要指定线程的执行函数、传递参数,并设置线程的属性(如优先级、栈大小等);
- 线程销毁:线程的销毁是指终止一个线程的执行,释放线程的资源。线程可以自行结束执行,也可以由其他线程强制终止。线程的资源清理通常包括释放内存、关闭文件句柄和释放其他系统资源;
- 线程同步:在线程间进行同步是为了保证共享资源的安全访问和避免竞争条件。常用的线程同步机制包括锁(如互斥锁、读写锁)、条件变量、信号量等,可以通过它们来实现线程的互斥访问和临界区的同步操作;
- 线程通信:线程通信是指线程之间的信息交流,共享数据和协作执行。线程通信的常用机制包括共享内存、消息队列、管道、信号量、事件等。线程通信的目的是实现线程间的数据交换和任务协作;
- 互斥锁和条件变量:互斥锁(Mutex)是一种常用的线程同步机制,它用于保护共享资源,在同一时间只允许一个线程访问。条件变量(Condition Variable)用于线程间的等待和通知,它允许线程在某个条件满足时休眠等待,直到其他线程发出唤醒通知;
- 线程池:线程池是一种管理和复用线程的机制,可以减少线程创建和销毁的开销,提高线程的利用率。线程池可以通过预先创建一组线程并维护线程队列,根据任务的到达情况动态分配线程进行处理;
- 线程安全性:线程安全是指多线程环境下能够正确执行并保证数据的一致性和正确性。编写线程安全的代码应考虑到并发访问共享资源的情况,并使用适当的同步机制来保证数据的同步和操作的原子性;
- 4.4 线程管理函数
- 4.4.1 创建线程
可以用于创建新的线程。
常见的创建线程函数有pthread_create
(POSIX标准的创建线程函数)、CreateThread
(Windows平台的创建线程函数)等。 - 4.4.2 线程ID
- 4.4.3 线程终止
线程的终止可以通过让线程执行完线程函数中的代码,或者显式地终止线程。
线程完成后,可以使用pthread_exit
(POSIX标准的终止线程函数)或ExitThread
(Windows平台的终止线程函数)来终止线程。 - 4.4.4 线程连接
- 4.4.1 创建线程
- 4.5 线程示例程序
- 4.5.1 用线程计算矩阵的和
下面是一个使用线程计算矩阵和的实例(使用C语言和pthread库):
- 4.5.1 用线程计算矩阵的和
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
// 定义矩阵大小
#define ROWS 3
#define COLS 3
// 定义线程参数结构体
typedef struct {
int row_start;
int row_end;
int (*matrix)[COLS];
int *sum;
} ThreadArgs;
// 线程函数,计算矩阵子区域的和
void* calculateSum(void* arg) {
ThreadArgs* args = (ThreadArgs*)arg;
int row_start = args->row_start;
int row_end = args->row_end;
int (*matrix)[COLS] = args->matrix;
int *sum = args->sum;
// 计算矩阵子区域的和
for (int i = row_start; i <= row_end; i++) {
for (int j = 0; j < COLS; j++) {
(*sum) += matrix[i][j];
}
}
pthread_exit(NULL);
}
int main() {
int matrix[ROWS][COLS] = {{1, 2, 3},
{4, 5, 6},
{7, 8, 9}};
int sum = 0;
// 创建线程参数数组
ThreadArgs thread_args[2];
// 创建线程池
pthread_t threads[2];
// 计算矩阵子区域的和
for (int i = 0; i < 2; i++) {
thread_args[i].row_start = i * (ROWS / 2);
thread_args[i].row_end = (i + 1) * (ROWS / 2) - 1;
thread_args[i].matrix = matrix;
thread_args[i].sum = ∑
pthread_create(&threads[i], NULL, calculateSum, (void*)&thread_args[i]);
}
// 等待线程结束
for (int i = 0; i < 2; i++) {
pthread_join(threads[i], NULL);
}
printf("Sum: %d
", sum);
return 0;
}
在这个实例中,首先定义了一个3x3的矩阵,并且定义了一个表示线程参数的结构体ThreadArgs。calculateSum函数是线程函数,用于计算矩阵子区域的和。在main函数中,首先创建了线程参数数组和线程池。然后,使用循环创建两个线程,每个线程计算矩阵的一部分。最后,使用循环等待线程结束,并打印出计算得到的矩阵和。在计算矩阵和的过程中,多个线程可以并发地计算各自的子区域,从而提高计算效率。
- 4.5.2 用线程快速排序
以下是一个使用线程进行快速排序的实例(使用C语言和pthread库):
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define ARRAY_SIZE 10
// 线程参数结构体
typedef struct {
int* array;
int left;
int right;
} ThreadArgs;
// 快速排序函数
void quickSort(int* array, int left, int right) {
if (left >= right) return;
int pivot = array[left];
int i = left, j = right;
while (i < j) {
while (i < j && array[j] >= pivot) j--;
if (i < j) array[i++] = array[j];
while (i < j && array[i] < pivot) i++;
if (i < j) array[j--] = array[i];
}
array[i] = pivot;
quickSort(array, left, i - 1);
quickSort(array, i + 1, right);
}
// 线程函数,用于快速排序
void* sortThread(void* arg) {
ThreadArgs* args = (ThreadArgs*)arg;
int* array = args->array;
int left = args->left;
int right = args->right;
quickSort(array, left, right);
pthread_exit(NULL);
}
int main() {
int array[ARRAY_SIZE] = {4, 7, 1, 3, 9, 2, 6, 5, 8, 0};
// 创建线程参数
ThreadArgs thread_args;
thread_args.array = array;
thread_args.left = 0;
thread_args.right = ARRAY_SIZE - 1;
// 创建线程
pthread_t thread;
pthread_create(&thread, NULL, sortThread, (void*)&thread_args);
// 等待线程结束
pthread_join(thread, NULL);
// 打印排序结果
printf("Sorted array: ");
for (int i = 0; i < ARRAY_SIZE; i++) {
printf("%d ", array[i]);
}
printf("
");
return 0;
}
在这个实例中,首先定义了一个包含10个元素的整数数组,并定义了一个表示线程参数的结构体ThreadArgs。quickSort函数是用来进行快速排序的函数。sortThread函数是线程函数,用于在指定的区间内进行快速排序。在main函数中,首先创建了线程参数,并创建了一个线程,将其作为参数传递给sortThread函数。然后,使用pthread_join函数等待线程结束。最后,打印出排序后的结果。在进行快速排序的过程中,多个线程可以并发地对不同的区间进行排序,从而提高排序效率。
- 4.6 线程同步
- 4.6.1 互斥量
- 4.6.2 死锁预防
- 4.6.3 条件变量
条件变量是用来实现线程之间的协作与同步的机制。它允许线程在某个条件满足时等待,同时也允许其他线程在满足特定条件时通知等待线程继续执行。常见的条件变量有条件变量(condition variable)和事件(event),通过等待(wait)和通知(signal)操作来实现对条件变量的控制。 - 4.6.4 生产者-消费者问题
- 4.6.5 信号量
信号量是用来实现线程同步的一种机制。它允许多个线程在共享资源上进行协调,限制同时访问共享资源的线程数目。常见的信号量有计数信号量(counting semaphore)和互斥信号量(binary semaphore),通过等待(wait)和发送(signal)操作来实现对信号量的控制。 - 4.6.6 屏障
- 4.6.7 用并发线程解线性方程组
- 4.6.8 Linux中的线程