并发编程
在只有一个CPU的情况下,每次只能按顺序执行某算法的一个指令和步骤。但是,基于分治原则(如二叉树查找和快速排序等)的算法经常表现出高度的并行性,可通过使用并行或并发执行来提高计算速度。并行计算是一种计算方案,它尝试使用多个执行并行算法的处理器更快速地解决问题。显然,计算的未来发展方向是并行计算。因此,迫切需要在计算机科学和计算机工程专业学生的早期学习阶段引入并行计算。
顺序算法与并行算法
顺序算法
begin
step_1
step_2
···
step_n
end
并行算法
cobegin
task_1
task_2
···
task_n
coend
顺序算法可能包含多个步骤。所有步骤都是通过单个任务依次执行的, 每次执行一个步骤。当所有步骤执行完成时,算法结束。相反,并行算法使用cobegin-coend代码块来指定并行算法的独立任务。在cobegin-coend块中.所有任务都是并行执行的,紧接着cobegin-coend代码块的下一个步骤将只在所有这些任务完成之后执行。
并行性与并发性
通常,并行算法只识别可并行执行的任务,但是它没有规定如何将任务映射到处理组件。在理想情况下,并行算法中的所有任务都应该同时实时执行。然而,真正的并行执行只曲在有多个处理组件的系统中实现,比如多处理器或多核系统。在单CPU系统中,一次只能执行一个任务。在这种情况下,不同的任务只能并发执行,即在逻辑上并行执行。
线程的原理
一个操作系统(OS)包含许多并发进程在进程模型中,进程是独立的执行单元。所有进程均在内核模式或用户模式下执行。在内核模式下,各进程在唯一地址空间上执行,与其他进程是分开的。虽然每个进程都是一个独立的单元,但是它只有一个执行路径。线程共享进程的许多其他资源,如用户id、打开的文件描述符和信号等。打个简单的比方,进程是一个有房屋管理员(主线程)的房子,线程是住在进程房子里的人。房子里的每个人都可以独立做自己的事情,但是他们会共用一些公用设施,比如同一个信箱、厨房和浴室等。
线程的优点
- 线程创建和切换速度更快:进程的上下文复杂而庞大。其复杂性主要来自管理进程映像的需要。
- 线程的响应速度更快:一个进程只有一个执行路径。当某个进程被挂起时,整个进程都将停止执行。相反,当某个线程被挂起时,同进程中的其他线程可以继续执行。这使得有多个线程的程序响应速度更快。
- 线程更适合并行计算:并行计算的目标是使用多个执行路径更快地解决问题。基于分治原则(如二叉树查找和快速排序等)的算法经常表现出高度的并行性,可通过使用并行或并发执行来提高计算速度。这种算法通常要求执行实体共享公用数据。
线程的缺点
- 由于地址空间共享,线程需要来自用户的明确同步。
- 许多库函数可能对线程不安全,例如传统strtok()函数将一个字符串分成一连串令牌。通常,任何使用全局变量或依赖于静态内存内容的函数.线程都不安全。为了使库函数适应线程环境,还需要做大量的工作。
- 在单CPU系统上,使用线程解决问题实际上要比使用顺序程序慢,这是由在运行时创建线程和切换上下文的系统开销造成的。
线程管理函数
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);
- pthread_id是指向pthread_t类型变量的指针。它会被操作系统内核分配的唯一线程ID填充。在POSIX中,pthread_t是一种不透明的类型。程序员应该不知道不透明对象的内容,因为它可能取决于实现情况。线程可通过pthread_self()函数获得自己的ID。在Linux中,pthreadj类型被定义为无符号长整型,因此线程1D可以打印为%111。
- attr是指向另一种不透明数据类型的指针,它指定线程属性,下面将对此进行更详细的说明。
- fimc是要执行的新线程函数的入口地址。
- arg是指向线程函数参数的指针,可写为:
void *func(void *arg)
其中,attr参数最复杂。下面给出了attr参数的使用步骤。
- 定义一个pthread属性变量pthread_attr_t attr。
- 用pthread_attr_init(&attr)初始化属性变量。
- 设置属性变量并在pthread_create()调用中使用。
- 必要时,通过pthread_attr_destroy(&attr)释放attr资源。
下面的代码段显示了如何创建一个分离线程。
pthread_attr_t attr; // define an attr variable
pthread_attr_init(&attr); // initialize attr
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); // set attr pthread_create(&thread_idz func, NULL); // create thread with attr
pthread_attr_destroy; // optional: destroy attr
每个线程都使用默认堆栈的大小来创建。在执行过程中,线程可通过函数找到它的堆栈大小:
size_t pthread_attr_getstacksize()
它可以返回默认的堆栈大小。下面的代码段显示了如何创建具有特定堆栈大小的线程。
pthread_attr_t attr; // attr variable
size_t stacksize; // stack size
pthread_attr_init(&attr); // initialize attr
stacksize = 0x10000; // stacksize=16KB;
pthread_attr_setstacksize(ftattr, stacksize); // set stack size in attr
pthread_create(&threads[t], &attr, func, NULL); // create thread with stack size
- 线程ID
int pthread_equal (pthread_t tl, pthread_t t2);
- 线程终止
int pthread_exit (void *status);
- 线程连接
int pthread_join (pthread_t thread, void **status_ptr);
实践代码:
#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("]
");
}
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
",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
" , 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 ("20191304ssh
") ;
}
截图: