自学教材第4章,提交学习笔记(10分),评分标准如下 1. 知识点归纳以及自己最有收获的内容,选择至少2个知识点利用chatgpt等工具进行苏格拉底挑战,并提交过程截图,提示过程参考下面内容 (4分) “我在学***X知识点,请你以苏格拉底的方式对我进行提问,一次一个问题” 核心是要求GPT:“请你以苏格拉底的方式对我进行提问” 然后GPT就会给你提问,如果不知道问题的答案,可以反问AI:“你的理解(回答)是什么?” 如果你觉得差不多了,可以先问问GPT:“针对我XXX知识点,我理解了吗?” GPT会给出它的判断,如果你也觉得自己想清楚了,可以最后问GPT:“我的回答结束了,请对我的回答进行评价总结”,让它帮你总结一下。 2. 问题与解决思路,遇到问题最先使用chatgpt等AI工具解决,并提供过程截图(3分) 3. 实践过程截图,代码链接(2分) 4. 其他(知识的结构化,知识的完整性等,提交markdown文档,使用openeuler系统等)(1分)
一、课本第四章内容总结
4.1并行计算导论
顺序算法与并行算法
并行性与并发性
通常,并行算法只识别可并行执行的任务,但是它没有规定如何将任务映射到处理组件。在理想情况下,并行算法中的所有任务都应该同时实时执行。然而,真正的并行执行只能在有多个处理组件的系统中实现,比如多处理器或多核系统。在单 CPU 系统中,一次只能执行一个任务。在这种情况下,不同的任务只能并发执行、即在逻辑上并行执行。在单CPU系统中,并发性是通过多任务处理来实现的。
4.2线程
原理
线程是某进程同一地址空间上的独立执行单元。
创建某个进程就是在一个唯一地址空间创建一个线程。当某进程开始时,就会执行该进程的主线程。如果只有一个主线程,那么进程和线程实 际上并没有区别。但是, 主线程可能会创建其他线程。 每个线程又可以创建更多的线程等。 某进程的所有线程都在该进程的相同地址空间中执行, 但每个线程都是一个独立的执行单元。
优点
与进程相比,有以下优点:
(1)创建和切换速度更快
(2)响应速度更快
(3)更适合并行计算
缺点
由于地址空间共享,线程需要来自用户的明确同步
许多库函数可能对线程不安全,例如strtok();
在单CPU系统上,使用线程解决间题实际上要比使用顺序程序慢,这是由在运行时创建线程和切换上下文的系统开销造成的。
4.3线程管理函数
1.创建线程
使用pthread_create()函数创建线程
如果成功则返回0,如果失败则返回错误代码。
其中,pthread_create()的参数为:
- pthread_id是指向pthread_t类型变员的指针。它会被操作系统内核分配的唯一线程ID填充。在POSIX中,pthread_t是一种不透明的类型。线程可通过pthread_self()函数获得自己的ID。在Linux中,pthread_t类型被定义为无符号长整型,因此线程ID可以打印为%lu。
- attr是指向另一种不透明数据类型的指针,它指定线程属性。
- func是要执行的新线程函数的入口地址。
- arg是指向线程函数参数的指针,可写为
void *func(void *arg)
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
2.线程ID
使用pthread_equal()函数对线程ID进行比较
int pthread_equal(pthread_t t1,pthread_t t2);
3.线程终止
void pthread_exit(void *status);
4.线程连接
int pthread_join (pthread_t thread, void **status ptr);
4.4线程示例程序
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #define N 4 int A[N][N],sum[N]; void *func(void *arg) { int j,row ; pthread_t tid = pthread_self(); row = (int)arg; printf("Thread %d [%lu] computes sum of row %d\n",row,tid,row); for(j=0;j<N; j++) sum[row] += A[row][j]; printf("Thread %d [%lu] done:sum [%d] =%d\n",row,tid,row,sum[row]); pthread_exit ((void*)0); } int main(int argc, char *argv[]) { pthread_t thread[N]; int i,j,r,total = 0; void *status; printf("Main: initialize A matrix\n"); for(i=0; i<N;i++){ sum[i] = 0; for(j=0;j<N;j++){ A[i][j]=i*N+j+1; printf("%4d ",A[i][j]); } printf( "\n" ); } printf ("Main: create %d threads\n",N); for(i=0;i<N;i++) { pthread_create(&thread[i],NULL,func,(void *)i); } printf("Main: try to join with thread\n"); for(i=0; i<N; i++) { pthread_join(thread[i],&status); printf("Main: joined with %d [%lu]: status=%d\n",i,thread[i], (int)status); } printf("Main: compute and print total sum:"); for(i=0;i<N;i++) total += sum[i]; printf ("tatal = %d\n",total ); pthread_exit(NULL); }
4.5线程同步
互斥量
最简单的同步工具是锁,它允许执行实体仅在有锁的情况下才能继续执行。在Pthread中,锁被称为互斥量。在使用之前必须对他们进行初始化。
死锁预防
互斥量使用封锁协议。如果某线程不能获取互斥量,就会被阻塞,等待互斥量解锁后再继续。在任何封锁协议中,误用加锁可能会产生一些问题。最常见和突出的问题是死锁。有多种方法可以解决可能的死锁问题,其中包括死锁预防、死锁规避、死锁检测和恢复等。
在实际系统中,唯一可行的方法是死锁预防,试图在设计并行算法时防止死锁的发生。一种简单的死锁预防方法是对互斥量进行排序,并确保每个线程只在一个方向请求互斥量,这样请求序列中就不会有循环。
条件变量
作为锁,互斥量仅用于确保线程只能互斥地访问临界区中的共享数据对象。条件变量提供了一种线程协作的方法。在Pthread中,使用类型pthread_cond_t来声明条件变量,而且必须在使用前进行初始化。
条件变量可以通过两种方法进行初始化