信息安全系统实现与设计第八周——《Unix/Linux系统编程》第四章自学
第四章 并发编程
这一章主要介绍了并发编程的相关内容,包括并行计算、顺序算法与并行算法以及并行性和并发性;解释了线程的原理机器相对于进程的优势,同时还进行了线程管理、并发编程的实际操作,让我们更加深刻地了解多任务处理、线程同步和并发编程的原理及方法。
4.1 并行计算
(1)背景:
早期计算机只有一个处理组件,称为处理器或中央处理器,程序多进行串行计算,而基于分治原则的算法具有高度并行性,因此出现尝试使用多个执行并行算的处理器来解决问题的并行计算方案。
(2)顺序算法与并行算法
顺序算法所有步骤都是通过单个步骤依次执行的,所有步骤执行完成则算法结束。
并行算法中同一个代码块中的步骤并行执行,在全部完成后再执行下一个代码块的步骤。
4.2 线程
(1)线程原理
一个操作系统包含许多并发进程。在进程模型中,进程是独立的执行单元。所有进程均是在内核模式和用户模式下进行的。
内核模式下:各进程在唯一地址空间上执行,与其他进程是分开的,多个独立的进程共用同一个执行路径,当某进程必须等待某事件时,整个进程就会停止执行。
线程是某进程同一地址空间上的独立执行单元。创建某个进程就是在一个唯一地址空间创建一个主线程。
(2)线程优点
1.线程创建和切换速度更快
2.线程的响应速度更快
3.线程更适合并行计算
(3)线程缺点
1.由于地址空间共享,县城需要来自用户的明确同步
2.许多库函数可能对线程不安全
3.在单CPU系统上,使用线程解决问题实际上要比使用顺序程序更慢。
4.3 线程操作
线程的执行轨迹与进程类似。用户可在内核模式或用户模式下进行
用户模式下,线程在进程的相同地址空间中执行,但每个线程都有自己的执行堆栈。线程是独立的执行单元,可根据操作系统内核调度策略,对内核进行系统调用,变为挂起、激活已继续执行。
4.4 线程管理函数
1、创建线程 使用pthread_create()函数创建线程
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);
不同线程返回0,否则返回非0 `
3、线程终止 线程可以调用函数进行终止
void pthread_exit(void *status);
0退出值表示正常终止,非0值表示异常终止
4、线程连接 一个线程可以等待另一个线程的终止,通过函数终止线程的退出状态。
int pthread_join (pthread_t thread, void **status ptr); `
4.5 线程同步
当多个线程试图修改同一地址共享变量或数据结构时,如果修改结果取决于线程的执行顺序,则称之为静态条件。
(1)互斥量
最简单的同步工具是锁,它允许执行实体仅在有锁的情况下才能继续执行。在Pthread中,锁被称为互斥量。在使用之前必须对他们进行初始化。
有两种方法可以初始化互斥址:
静态方法:pthreaa—mutex_t m = PTHREAD_MUTEX_INITIALIZER;
定义互斥量 m, 并使用默认属性对其进行初始化。
动态方法:使用 pthread_ mutex _init() 函数,可通过attr参数设置互斥属性。
(2)死锁预防
互斥量使用封锁协议。如果某线程不能获取互斥量,就会被阻塞,等待互斥量解锁后再继续。在任何封锁协议中,误用加锁可能会产生一些问题。最常见和突出的问题是死锁。
有多种方法可以解决可能的死锁问题,其中包括死锁预防、死锁规避、死锁检测和恢复等。
在实际系统中,唯一可行的方法是死锁预防,试图在设计并行算法时防止死锁的发生。一种简单的死锁预防方法是对互斥量进行排序,并确保每个线程只在一个方向请求互斥量,这样请求序列中就不会有循环。
(3)条件变量
作为锁,互斥量仅用于确保线程只能互斥地访问临界区中的共享数据对象。条件变量提供了一种线程协作的方法。
在Pthread中,使用类型pthread_cond_t来声明条件变量,而且必须在使用前进行初始化。 条件变量可以通过两种方法进行初始化 :
静态方法 :在声明时,定义一个条件变量con,并使用默认属性对其进行初始化,如:
pthread_cond_t con = PTHREAD_COND_INITALLIZER;
动态方法 :使用pthread_cond_init()函数,通过attr参数设置条件变量。
(4)信号量
信号量与条件变量的主要区别在于,前者包含一个计数器,可操作性计数器,测试计数器值以作出决策,而后者需要一个特定的互斥量来执行临界区。
5.1 实践
根据书中示例4.2,最终得到代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
typedef struct{
int upperbound;
int lowerbound;
}PARM;
#define N 10
int a[N]={5,1,6,4,7,2,9,8,0,3};// unsorted data
int print(){//print current a[] contents
int i;
printf("[");
for(i=0;i<N;i++)
printf("%d ",a[i]);
printf("]\n");
}
void *Qsort(void *aptr){
PARM *ap, aleft, aright;
int pivot, pivotIndex,left, right,temp;
int upperbound,lowerbound;
pthread_t me,leftThread,rightThread;
me = pthread_self();
ap =(PARM *)aptr;
upperbound = ap->upperbound;
lowerbound = ap->lowerbound;
pivot = a[upperbound];//pick low pivot value
left = lowerbound - 1;//scan index from left side
right = upperbound;//scan index from right side
if(lowerbound >= upperbound)
pthread_exit (NULL);
while(left < right){//partition loop
do{left++;} while (a[left] < pivot);
do{right--;}while(a[right]>pivot);
if (left < right ) {
temp = a[left];a[left]=a[right];a[right] = temp;
}
}
print();
pivotIndex = left;//put pivot back
temp = a[pivotIndex] ;
a[pivotIndex] = pivot;
a[upperbound] = temp;
//start the "recursive threads"
aleft.upperbound = pivotIndex - 1;
aleft.lowerbound = lowerbound;
aright.upperbound = upperbound;
aright.lowerbound = pivotIndex + 1;
printf("%lu: create left and right threadsln", me) ;
pthread_create(&leftThread,NULL,Qsort,(void * )&aleft);
pthread_create(&rightThread,NULL,Qsort,(void *)&aright);
//wait for left and right threads to finish
pthread_join(leftThread,NULL);
pthread_join(rightThread, NULL);
printf("%lu: joined with left & right threads\n",me);
}
int main(int argc, char *argv[]){
PARM arg;
int i, *array;
pthread_t me,thread;
me = pthread_self( );
printf("main %lu: unsorted array = ", me);
print( ) ;
arg.upperbound = N-1;
arg. lowerbound = 0 ;
printf("main %lu create a thread to do QS\n" , me);
pthread_create(&thread,NULL,Qsort,(void * ) &arg);//wait for Qs thread to finish
pthread_join(thread,NULL);
printf ("main %lu sorted array = ", me);
print ("20191303jcy\n") ;
}
实践截图如下:
5.2问题出现与解决
具体操作时出现如截图1中的报错,经过询问同学,判断为编译出现问题,由于涉及到了线程的POSIX标准,因此在Unix操作系统中使用Pthread命令行工具需要使用
gcc -pthread
命令进行编译,最终得到结果如上图2