学习笔记7

第四章 并发编程

概述

本章论述了并发编程,介绍了并行计算的概念,指岀了并行计算的重要性;比较了顺序 算法与并行算法,以及并行性与并发性;解释了线程的原理及其相对于进程的优势;通过示 例介绍了 Pthread中的线程操作,包括线程管理函数,互斥量、连接、条件变量和屏障等线 程同步工具;通过具体示例演示了如何使用线程进行并发编程,包括矩阵计算、快速排序和 用并发线程求解线性方程组等方法;解释了死锁问题,并说明了如何防止并发程序中的死锁 问题;讨论了信号量,并论证了它们相对于条件变量的优点;还解释了支持Linux中线程的 独特方式。

并行计算导论

基于分治 原则(如二叉树查找和快速排序等)的算法经常表现出高度的并行性,可通过使用并行或并 发执行来提高计算速度。并行计算是一种计算方案,它尝试使用多个执行并行算法的处理器 更快速地解决问题。

顺序算法与并行算法

  • 顺序算法
begin
    step_1
    step_2
    ···
    step_n
  end
  • 并行算法
 cobegin
    task_1
    task_2
    ···
    task_n
  coend
  • 顺序算法begin-end代码块中的顺序算法可能包含多个步骤。所有步骤都是通过单个任务依次执行的,每次执行一个步骤。当所有步骤执行完成时,算法结束。相反,并行算法使用cobegin-coend代码块来指定并行算法的独立任务。在cobegin-coend块中,所有任务都是并行执行的。紧接着cobegin-coend代码块的下一个步骤将只在所有这些任务完成之后执行。

并行性与并发性

并行算法只识别可并行执行的任务,但是它没有规定如何将任务映射到处理组件。在理想情况下,并行算法中的所有任务都应该同时实时执行。真正的并行执行只在有多个处理组件的系统中实现,比如多处理器或多核系统。在单CPU系统中,一次只能执行一个任务。在这种情况下,不同的任务只能并发执行,即在逻辑上并行执行。在单 CPU系统中,并发性是通过多任务处理来实现的

线程

原理

在进程模型中,进程是独立的执行单元。所 有进程均在内核模式或用户模式下执行。在内核模式下,各进程在唯一地址空间上执行,与 其他进程是分开的。虽然每个进程都是一个独立的单元,但是它只有一个执行路径。当某进 程必须等待某事件时,例如I/O完成事件,它就会暂停,整个进程会停止执行匸线程是某进 程同一地址空间上的独立执行单元。

某进程的所有线程都在该进程的相同地址空间中执行,但每个线程都是一个独立的执行单 元。在线程模型中,如果一个线程被挂起,其他线程可以继续执行。

优点

线程创建和切换速度更快
线程的响应速度更快
线程更适合并行计算

缺点

由于地址空间共享,线程需要来自用户的明确同步。
许多库函数可能对线程不安全
在单CPU系统上,使用线程解决问题实际上要比使用顺序程序慢,这是由在运行 时创建线程和切换上下文的系统开销造成的。

线程操作

线程可在内核模式或用户模式下执行 在用户模式下,线 程在进程的相同地址空间中执行,但每个线程都有自己的执行堆栈。线程是独立的执行単 元,可根据操作系统内核的调度策略,对内核进行系统调用,变为挂起、激活以继续执行等。

操作系统内核的调度策略可能会优先选择同-进程中的 线程,而不是不同进程中的线程。截至目前,几乎所有的操作系统都支持POSIX Pthread, 定义了一系列标准应用程序编程接口(API)来支持线程编程。

进程管理函数

pthread_create(thread, attr, function, arg): create thread pthread_exit(status)	:	terminate thread
pthread_cancel(thread)	:	cancel thread
pthread_attr_init(attr)	:	initialize thread	attributes
pthread_attr_destroy(attr): destroy thread attribute

创建线程

  • 使用pthread_create()函数创建线程
int pthread_create (pthread_t *pthread_id, pthread_attr_t *attr, 
void *(*func)(void *), void *arg);
  • thread_id是指向pthread_t类型变量的指针。它会被操作系统内核分配的唯一线程ID填充。在POSIX中,pthread_t是一种不透明的类型。程序员应该不知道不透明对象的内容,因为它可能取决于实现情况。线程可通过pthread_self()函数获得自己的ID。在 Linux 中,pthread_t类型被定义为无符号长整型,因此线程ID可以打印为%lu。
  • attr是指向另一种不透明数据类型的指针,它指定线程属性,下面将对此进行更详细的说明。
  • func是要执行的新线程函数的入口地址。 arg是指向线程函数参数的指针,可写为:void *func(void *arg)
  • attr参数
    (1)定义一个pthread属性变量pthread_attr_t attr。
    (2)用pthread_attr_init (&attr)初始化属性变量。
    (3)设置属性变量并在pthread_create()调用中使用。
    (4)必要时,通过pthread_attr_destroy (&attr)释放attr资源。

线程 ID

线程ID是一种不透明的数据类型,取决于实现情况。因此,不应该直接比较线程ID。如果需要,可以使用pthread_equal()函数对它们进行比较。

int pthread_equal (pthread_t t1, pthread_t t2);

如果是不同的线程,则返回0,否则返回非0。

线程终止

一个线程可以等待另一个线程的终止,通过

int pthread_join (pthread_t thread, void **status ptr);

终止线程的退出状态以status_ptr返回。

线程同步

由于线程在进程的统一地址空间中运行,所以同一进程的所有线程都共享所有全局变量和数据结构。在多个线程都需要修改同一个变量或者是数据结构的时候就会产生竞争。要防止出现这样的问题,需要线程同步。
最简单的方法就是加锁。锁被称为互斥量。可以使用

静态的方法,定义互斥量m。
动态方法,使用pthread_mutex_init()函数,设置互斥属性。
或者使用条件变量的方法。同样分为静态和动态两种方式。

生产者——消费者问题

一系列生产者和消费者进程共享数量有限的缓冲区。每个缓冲区每次有一个特定的项目。也称有限缓冲问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。

信号量

在操作系统中我们学习过信号量的知识。信号量代表了一个资源的可用程度,是进程同步的一般机制。有P原语和V原语,原语都是原子操作或基本操作。

实践线程快速排序

image

posted @   池鱼12156  阅读(16)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
点击右上角即可分享
微信分享提示