第八周Linux教材第四章学习笔记——并发编程

发布时间 2023-10-28 20:08:47作者: 20211115fyq

 第四章  并发编程

4.1 并行计算导论

在早期,大多数计算机只有一个处理组件,称为处理器或中央处理器(CPU)。受这种硬件条件的限制,计算机程序通常是为串行计算编写的。要求解某个问题,先要设计一种算法,描述如何一步步地解决问题,然后用计算机程序以串行指令流的形式实现该算法。在只有一个CPU的情况下,每次只能按顺序执行某算法的一个指令和步骤。但是,基于分治原则(如二叉树查找和快速排序等)的算法经常表现出高度的并行性,可通过使用并行或并发执行来提高计算速度。并行计算是一种计算方案,它尝试使用多个执行并行算法的处理器更快速地解决问题。

4.1.1 顺序算法与并行算法

顺序算法:begin-end代码块中的顺序算法可能包含多个步骤。所有步骤都是通过单个任务依次执行的,每次执行一个步骤。当所有步骤执行完成时,算法结束。

begin
       step_1
       step_2
        ……
       step_n
end
//next step

并行算法:cobegin-coend代码块来指定并行算法的独立任务。在cobegin-coend块中,所有任务都是并行执行的。紧接着cobegin-coend代码块的下一个步骤将只在所有这些任务完成之后执行。

cobegin
       task_1
       task_2
       ……
       task_n
coend
//next step

4.1.2 并行性与并发性

在单CPU系统中,一次只能执行一个任务。在这种情况下,不同的任务只能并发执行,即在逻辑上并行执行。在CPU系统中,并发性是通过多任务处理来实现的。

4.2 线程

 PCB1相当于主线程,新线程PCB2、PCB3、PCB4相当于用vfork创建出来的,它们指向同一块地址空间,它们隶属于同一个进程,但是他们有着自己的线程ID

4.2.1 线程的原理

  1. 一个操作系统(OS)包含许多并发进程。在进程模型中,进程是独立的执行单元。所有进程均在内核模式或用户模式下执行。
  2. 在内核模式下,各进程在唯一地址空间上执行,与其他进程是分开的。虽然每个进程都是一个独立的单元,但是它只有一个执行路径。当某进程必须等待某事件时,例如I/O完成事件,它就会暂停,整个进程会停止执行。
  3. 线程是某进程同一地址空间上的独立执行单元。创建某个进程就是在一个唯一地址空间创建一个主线程。
  4. 当某进程开始时,就会执行该进程的主线程。如果只有一个主线程,那么进程和线程实际上并没有区别。但是,主线程可能会创建其他线程。每个线程又可以创建更多的线程等。某进程的所有线程都在该进程的相同地址空间中执行,但每个线程都是一个独立的执行单元。
  5. 在线程模型中,如果一个线程被挂起,其他线程可以继续执行。除了共享共同的地址空间之外,线程还共享进程的许多其他资源,如用户 id,打开的文件描述符和信号等。
  6. 打个简单的比方,进程是一个有房屋管理员(主线程)的房子。线程是住在进程房子里的人。房子里的每个人都可以独立做自己的事情,但是他们会共用一些公用设施,比如同一个信箱、厨房和浴室等。

4.2.2 线程的优点

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

4.2.3 线程的缺点

  1. 由于地址空间共享,线程需要来自用户的明确同步。
  2. 许多库函数可能对线程不安全。任何使用全局变量或依赖于静态内存内容的函数,线程都不安全。为了使库函数适应线程环境,还需要做大量的工作。
  3. 在单CPU系统上,使用线程解决问题实际上要比使用顺序程序慢,这是由在运行时创建线程和切换上下文的系统开销造成的。

4.3 线程操作

  1. 线程的执行轨迹与进程类似。线程可在内核模式或用户模式下执行。
  2. 在用户模式下,线程在进程的相同地址空间中执行,但每个线程都有自己的执行堆栈。
  3. 线程是独立的执行单元,可根据操作系统内核的调度策略,对内核进行系统调用,变为挂起、激活以继续执行等。
  4. 为了利用线程的共享地址空间,操作系统内核的调度策略可能会优先选择同一进程中的线程,而不是不同进程中的线程。

4.4 线程示例函数

4.4.1 创建线程

使用pthread_create()函数创建线程:成功返回0

int pthread_create ( pthread_t  *pthread_id,  pthread_attr_t  *attr,  void  *(*func)(void *),  void *arg);
  • pthread_id是指向pthread_t类型的指针。它会被操作系统内核分配的唯一线程ID填充。线程可通过pthread_self()函数来获得自己的ID,比如:pthread_t tid=pthread_self()。在Linux中,pthread_t类型被定义为无符号长整型,因此线程ID可以打印为%lu。
  • attr是另一种不透明数据类型的指针,它指定线程属性。如果attr参数为NULL,将使用默认属性创建线程。
  • func是要执行的新线程函数入口地址。
  • arg是指向线程函数参数的指针,可写为 void *func(void *arg)。

4.4.2 线程ID

线程ID是一种不透明的数据类型,取决于实际情况。使用pthread_equal()函数可以对线程ID进行比较。

int pthread_equal(pthread_t t1,pthread_t t2);

不同线程返回0,否则返回非0。

4.4.3 线程终止

线程函数结束后,线程即终止。或者线程可以调用函数

int pthread_exit  (void *status);

进行显示终止,其中状态是线程的退出状态。

返回0为正常终止,返回非0值为异常终止。

4.4.4 线程连接

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

int  pthread_join  (pthread_t thread,  void **status_ptr);

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

4.5 线程示例程序

4.6 线程同步

向ChatGpt请求苏格拉底式询问