学习笔记7

学习笔记 7

第四章 并发编程

4.1 并行计算导论

1.顺序算法与并行算法

  • 顺序算法:用一个begin-end代码块列出算法。可能包含多个步骤,所有步骤通过单个任务依次执行,每次执行一个步骤,全执行完,算法结束。

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

2.并行性与并发性

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

4.2 线程

1.线程的优点

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

2.线程的缺点

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

4.3 线程操作

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

4.4 线程管理函数

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    
  1. 创建线程
    使用pthread_create()函数创建线程。

     int pthread_create (pthread_t *pthread_id,pthread_attr_t *attr,void * (*func) (void *), void *arg);
    
  2. 线程 ID
    线程ID是一种不透明的数据类型,取决于实现情况。因此,不应该直接比较线程ID。如果需要,可以使用pthread _ equal()函数对它们进行比较。

     int pthread_equal (pthread_t t1, pthread_t t2);
    

如果是不同的线程,则返回0,否则返回非0。
3. 线程终止
线程函数结束后,线程即终止。或者,线程可以调用函数

	int pthread_exit (void *status);

进行显式终止,其中状态是线程的退出状态。通常,0退出值表示正常终止,非0值表示异常终止。
4. 线程连接
一个线程可以等待另一个线程的终止,通过

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

终止线程的退出状态以status _ ptr返回。

4.5 线程示例程序【实践】

  • 代码如下

      #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 () ;
      	}
    
  • 运行结果

4.6 线程同步

当多个线程试图修改同一共享变量或数据结构时,如果修改结果取决于线程的执行顺序,则称之为竞态条件。

1.互斥量

在 Pthread中,锁被称为互斥量,意思是相互排斥。互斥变量是用 ptbread _ mutex_t类型声明的,在使用之前必须对它们进行初始化。

  • 静态方法

      pthreaa—mutex_t m = PTHREAD_MUTEX_INITIALIZER  
    

定义互斥量 m, 并使用默认属性对其进行初始化。

  • 动态方法
    使用 pthread_ mutex _init() 函数。

2.死锁预防

互斥量使用封锁协议。有多种方法可以解决可能的死锁问题,其中包括死锁防御、死锁规避、死锁检测和回复等。在实际情况中,唯一可行的方法时死锁预防,试图在设计并行算法是防止死锁发生。一种简单的死锁预防时对互斥量进行排序,并确保每个线程只在一个方向请求互斥量,这样请求序列中就不会有循环。可利用条件加锁和退避预防死锁。

3.条件变量

作为锁,互斥量仅用于确保线程只能互斥地访问临界区中的共享数据对象。条件变量提供了一种线程协作的方法。在Pthread中,使用类型pthread_cond_t来声明条件变量,而且必须在使用前进行初始化。与互斥量一样,条件变量也可以通过两种方法进行初始化。

  1. 静态方法,在声明时,如:

     pthread_cond_t con = PTHREAD_COND_INITIALIZER;
    

定义一个条件变量con,并使用默认属性対其进行初始化。
2. 另一种是动态方法,使用pthread_cond_init()函数,可通过attr参数设置条件变量 为简便起见,我们总是使用NULL attr参数作为默认属性。

4.信号量

信号量是进程同步的一般机制。(计数)信号量是一种数据结构

struct sem{
  int value;
  struct process *queue
}s;  

5.屏障

线程连接操作允许某线程(通常是主线程)等待其他线程终止。在等待的所有线程都终 止后,主线程可创建新线程来继续执行并行程序的其余部分。创建新线程需要系统开销。在 某些情况下,保持线程活动会更好,但应要求它们在所有线程都达到指定同步点之前不能继续活动。在Pthreads中,可以采用的机制是屏障以及一系列屏障函数。

6.Linux中的线程

与许多其他操作系统不同,Linux不区分进程和线程。对于Linux内核,线程只是一个 与其他进程共享某些资源的进程。在Linux中,进程和线程都是由clone()系统调用创建的。

posted @ 2021-10-31 17:03  Kylin0  阅读(53)  评论(0编辑  收藏  举报