20191317王鹏宇第四章学习笔记
第四章:并发编程
知识点归纳总结:
本章论述了并发编程,介绍了并行计算的概念,指岀了并行计算的重要性;比较了顺序算法与并行算法,以及并行性与并发性;解释了线程的原理及其相对于进程的优势;
通过示例介绍了 Pthread中的线程操作,包括线程管理函数,互斥量、连接、条件变量和屏障等线程同步工具;通过具体示例演示了如何使用线程进行并发编程,包括矩阵计算、快速排序和用并发线程求解线性方程组等方法;
解释了死锁问题,并说明了如何防止并发程序中的死锁问题;讨论了信号量,并论证了它们相对于条件变量的优点;
解释了支持Linux中线程的独特方式。编程项目是为了实现用户级线程。它提供了一个基础系统来帮助读者开始工作。这个基础系统支持并发任务的动态创建、执行和终止,相当于在某个进程的同一地址空间中执行线程。读者可通过该项目实现线程同步的线程连接、互斥量和信号量,并演示它们在并发程序中的用法,该编程项目会让读者更加深入地了解多任务处理、线程同步和并发编程的原理及方法。
其中让我最有收获的几个部分如下:
- 线程
- 线程操作
- 线程管理函数
- 线程示例程序
- 线程同步
- 进程管理的系统调用
- I/O重定向
- 管道
线程的优点与缺点:
优点:
(1)线程创建和切换速度更快:进程的上下文复杂而庞大。其复杂性主要来自管理进程映像的需要。例如,在具有虚拟内存的系统中。进程映像可能由叫作页面的许多内存单元组成。在执行过程中。有些页面在内存中,有些则不在内存中。操作系统内核必须使用多个页表和多个级别的硬件辅助来跟踪每个进程的页面,要想创建新的进程,操作系统必须为进 程分配内存并构建页表。若要在某个进程中创建线程,操作系统不必为新的线程分配内存和创建页表,因为线程与进程共用同一个地址空间。所以,创建线程比创建进程更快。另外,由于以下原因,线程切换比进程切换更快。进程切换涉及将一个进程的复杂分贞环境 替换为另一个进程的复杂分页环境,需要大量的操作和时间。相比之下,同一个进程中的线程切换要简单得多、也快得多,因为操作系统内核只需要切换执行点,而不需要更改进程映像。
(2)线程的响应速度更快:一个进程只有一个执行路径。当某个进程被挂起时,整个进程都将停止执行。相反,当某个线程被挂起时,同进程中的其他线程可以继续执行。这使得有多个线程的程序响应速度更快.例如,在一个多线程的进程中,当一个线程被阻塞以等待I/O时,其他线程仍可在后台进行计算。在有线程的服务器中,服务器可同时服务多个客户机。
(3)线程更适合并行计算:并行计算的目标是使用多个执行路径更快地解决问题。基于分治原则(如二叉树查找和快速排序等)的算法经常表现出高度的并行性,可通过使用并行或并发执行来提高计算速度。这种算法通常要求执行实体共享公用数据。在进程模型中,各进程不能有效共享数据,因为它们的地址空间都不一样。为了解决这个问题,进程必须使用进程间通信(IPC)来交换数据或使用其他方法将公用数据区包含到其地址空间中。相反,同一进程中的所有线程共享同一地址空间中的所有(全局)数据。因此,使用线程编写并行执行的程序比使用进程编写更简单、更自然。
缺点:
(1)由于地址空间共享,线程需要来自用户的明确同步。
(2)许多库函数可能对线程不安全,例如传统strtok()函数将一个字符串分成一连串令 牌。通常,任何使用全局变量或依赖于静态内存内容的函数.线程都不安全。为了使库函数 适应线程环境,还需要做大量的工作。
(3)在单CPU系统上,使用线程解决问题实际上要比使用顺序程序慢,这是由在运行 时创建线程和切换上下文的系统开销造成的。
线程管理函数:
pthread_create
函数: 创建一个新的进程
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
Compile and link with -pthread.
pthread_exit
函数:退出当前线程
void pthread_exit(void *retval);
pthread_join
函数:以阻塞方式等待某一线程结束。
int pthread_join(pthread_t thread, void **retval);
pthread_cancel
函数:结束某个线程,只是请求取消运行
int pthread_cancel(pthread_t thread);
pthread_self
函数: 获取当前线程的TID
pthread_t pthread_self(void);
线程示例程序4.1:
/**** C4.l.c files compute matrix sum by threads****/
#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(); // get thread ID number
row = (int)arg; // get row number from 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); // thread exit: 0=normal termination
}
int main (int argc, char *argv[])
{
pthread_t thread[N]; // thread IDs
int i, j, r, total = 0;
void *status;
printf("Main: initialize A matrix\n",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 threads\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);
}
可以看出来编译失败,原因是没有找到pthread这个库,所以在编译时加上即可:gcc C4.1.c -o -C4.1 -pthread
线程示例程序4.2:
/****** C4.2.c: quicksort by threads *****/
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define N 10
typedef struct{
int upperbound;
int lowerbound;
}PARM;
int A[N]={5,1,6,4,7,2,9,8,0,3};
int print() // print current a[] contents
{
int i;
printf("[ ");
for (i=0; i<N; i++)
{
printf("%d ", A[i]);
}
printf("]\n");
}
void *qsort_1(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];
left = lowerbound - 1;
right = upperbound;
if (lowerbound >= upperbound)
pthread_exit(NULL);
while (left < right)
{
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;
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 threads\n", me);
pthread_create(&leftThread, NULL, qsort_1, (void *)&aleft);
pthread_create(&rightThread, NULL, qsort_1, (void *)&aright);// wait for left and right threads
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_1, (void *)&arg); // wait for QS thread to finish
pthread_join(thread, NULL);
printf("main %lu sorted array = ", me);
print();
}
这里出现了非常非常严重的错误,如果我们按照教材代码来写,会发现有很多a[]
但是实际上我们并没有定义过这样一个数组,经过分析,我认为这个a[]就是我们定义的全局变量int A[N]={5,1,6,4,7,2,9,8,0,3};
,果不其然在修改编译后跟教材实例的结果一样。
编译运行:gcc C4.2.c -o C4.2 -pthread
线程示例程序4.3:
/** C4.3.c: matrix sum by threads with mutex lock **/
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define N 4
int A[N][N];
int total = 0;
pthread_mutex_t *m;
void *func(void *arg)
{
int i, row, sum = 0;
pthread_t tid = pthread_self(); // get thread ID number
row = (int)arg; // get row number from arg
printf("Thread %d [%lu] computes sum of row %d\n", row, tid, row);
for (i=0; i<N; i++)
sum += A[row][i];
printf("Thread %d [%lu] update total with %d : Thread %d : ", row, tid, sum,row);
//pthread_mutx_lock(m);
pthread_mutex_lock(m);
total += sum;
pthread_mutex_unlock(m);
printf ("total = %d\n", total);
}
int main (int argc, char *argv[])
{
pthread_t thread[N];
int i, j, r;
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");
}
// create a mutex m
m = (pthread_mutex_t *)malloc(sizeof(pthread_mutex_t));
pthread_mutex_init(m, NULL); // initialize mutex m
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 threads\n");
for(i=0; i<N; i++)
{
pthread_join(thread[1], &status);
printf("Main: joined with %d [%lu]: status=%d\n",i, thread[i]/ (int)status);
}
printf("Main: tatal = %d\n", total);
pthread_mutex_destroy (m); // destroy mutex m
pthread_exit(NULL);
}
同样这个代码中也有错误,具体为main函数中出现了一个未定义的变量sum[],还有就是,将pthread_mutex_lock(m);
错打为了pthread_mutx_lock(m);
,将其修改后就恢复了正常:
线程示例程序4.4:
/* C4.4 .c: producer-consximer by threads with condition variables */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NBUF 5
#define N 10
// shared global variables
int buf[NBUF]; // circular buffers
int head, tail; // indices
int data; // number of full buffers
pthread_mutex_t mutex;
pthread_cond_t empty, full;
int init()
{
head = tail = data = 0;
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&full, NULL);
pthread_cond_init(&empty, NULL);
}
void *producer()
{
int i;
pthread_t me = pthread_self();
for (i=0; i<N; i++) // try to put N items into buf[]
{
pthread_mutex_lock(&mutex); // lock mutex
if (data == NBUF)
{
printf ("producer %lu: all bufs FULL: wait\n", me);
pthread_cond_wait(&empty, &mutex); // wait
}
buf[head++] = i+1;
head %= NBUF;
data++;
printf("producer %lu: data=%d value=%d\n", me, data, i+1);
pthread_mutex_unlock (&mutex);
pthread_cond_signal(&full);
}
printf("producer %lu: exit\n", me);
}
void *consumer()
{
int i, c;
pthread_t me = pthread_self();
for (i=0; i<N; i++)
{
pthread_mutex_lock(&mutex); // lock mutex
if (data == 0)
{
printf ("consumer %lu: all bufs EMPTY: wait\n", me);
pthread_cond_wait(&full, &mutex); // wait
}
c = buf[tail++]; // get an item
tail %= NBUF;
data--; // dec data by 1
printf("consumer %lu: value=%d\n", me, c);
pthread_mutex_unlock(&mutex); // unlock mutex
pthread_cond_signal(&empty); // unblock a producer, if any
}
printf("consumer %lu: exit\n", me);
}
int main ()
{
pthread_t pro, con;
init();
printf("main: create producer and consumer threads\n");
pthread_create(&pro, NULL, producer, NULL);
pthread_create(&con, NULL, consumer, NULL);
printf("main: join with threads\n");
pthread_join(pro, NULL);
pthread_join(con, NULL);
printf("main: exit\n");
}
这里同样有一个错误,第37行是bp->data,但是我们并没有定义bp这个指针,所以只需要将bp删掉即可:
线程示例程序4.5:
/*C4.5.C3 Gauss Elimination with Partial Pivoting **/
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <math.h>
#define N 4
double A[N][N+1];
pthread_barrier_t barrier;
int print_matrix()
{
int i, j;
printf("----------------------------\n");
for(i=0; i<N; i++)
{
for(j=0; j < N+1; j++)
printf("%6.2f ", A[i][j]);
printf("\n");
}
}
void *ge(void *arg) // threads function: Gauss elimination
{
int i, j, prow;
int myid = (int)arg;
double temp, factor;
for(i=0; i<N-1; i++)
{
if (i == myid)
{
printf("partial pivoting by thread %d on row %d: ", myid, i);
temp = 0.0; prow = i;
for (j=i; j<=N; j++)
{
if (fabs(A[j][i]) > temp)
{
temp = fabs(A[j][i]);
prow = j;
}
}
printf("pivot_row=%d pivot=%6.2f\n", prow, A[prow][i]);
if (prow != i) // swap rows
{
for (j=i; j<N+1; j++)
{
temp = A[i][j];
A[i][j] = A[prow][j];
A[prow][j] = temp;
}
}
}
// wait for partial pivoting done
pthread_barrier_wait(&barrier);
for(j=i+1; j<N; j++)
{
if (j == myid)
{
printf("thread %d do row %d\n", myid, j);
factor = A[j][i]/A[i][i];
for (int k=i+1; k<=N; k++)
A[j][k] -= A[i][k]*factor;
A[j][i] = 0.0;
}
}
// wait for current row reductions to finish
pthread_barrier_wait(&barrier);
if (i == myid)
print_matrix();
}
}
int main(int argc, char *argv[])
{
int i, j;
double sum;
pthread_t threads[N];
printf("main: initialize matrix A[N][N+l] as [A|B]\n");
for (i=0; i<N; i++)
for (j=0; j<N; j++)
A[i][j] = 1.0;
for (i=0; i<N; i++)
A[i][N-i-1] = 1.0*N;
for (i=0; i<N; i++)
{
A[i][N] = 2.0*N - 1;
}
print_matrix(); // show initial matrix [A|B]
pthread_barrier_init(&barrier, NULL, N); // set up barrier
printf("main: create N=%d working threads\n", N);
for (i=0; i<N; i++)
{
pthread_create(&threads[i], NULL, ge, (void *)i);
}
printf("main: wait for all %d working threads to join\n", N);
for (i=0; i<N; i++)
{
pthread_join(threads[i], NULL);
}
printf("main: back substitution :");
for (i=N-1; i>=0; i--)
{
sum = 0.0;
for (j=i+1; j<N; j++)
sum += A[i][N];
A[i][N] = (A[i][N]- sum)/A[i][i];
}
// print solution
printf("The solution is :\n");
for(i=0; i<N; i++)
{
printf("%6.2f ",A[i][N]);
}
printf("\n");
}
项目基本代码,一个多任务处理系统:
ts.sfile:
/******ts.s**************/
.global tswitch, running, scheduler
tswitch:
SAVE: pushal
pushfl
movl running,%ebx
movl %esp, 4(%ebx) # integers in GCC are 4 bytes
FIND: call scheduler
RESUME: movl running, %ebx
movl 4(%ebx), %esp
popfl
popal
ret
type.hfile:
/*********type.h******************/
#define NPROC 9
#define SSIZE 1024
// PROC status
#define FREE 0
#define READY 1
#define SLEEP 2
#define BLOCK 3
#define ZOMBIE 4
typedef struct proc{
struct proc *next; // next proc pointer
int ksp; // saved stack pointer
int pid; // proc PID
int priority; // proc scheduling priority
int status; // current status: FREE|READY, etc
int event; // for sleep/wakeup
int exitstatus; // exit status
int joinPid; // join target pid
struct proc *joinPtr; // join target PROC pointer
int stack[SSIZE]; // proc 4KB stack area
}PROC;
//#include "type.h"
/************queue file*********/
int enqueue(PROC **queue, PROC *p)
{
PROC *q = *queue;
if (q == 0|| p->priority > q->priority)
{
*queue = p;
p->next =q;
}
else
{
while (q->next && p->priority <= q->next->priority)
{
q = q->next;
}
p->next = q->next;
q->next = p;
}
}
PROC *dequeue(PROC **queue)
{
PROC *p = *queue;
if (p)
{
*queue = (*queue)->next;
}
return p;
}
int printList(char *name, PROC *p)
{
printf("%s = ", name);
while (p)
{
printf("[%d %d]->",p->pid, p->priority);
p = p->next;
}
printf("NULL\n");
}
C4.7t.cfile:
#include <stdio.h>
#include "4.7type.h"
PROC proc[NPROC]; // NPROC proc structures
PROC *freeList; // free PROC list
PROC * readyQueue;
PROC *sleepList;
PROC *running;
#include "queue.c"
void func(void *parm);
int create(void (*f)(), void *parm);
int init()
{
int i, j;
PROC *p;
for (i=0; i<NPROC; i++)
{
p = &proc[i];
p->pid = i;
p->priority = 0;
p->status = FREE;
p->event = 0;
p->joinPid = 0;
p->joinPtr = 0;
p->next = p+1;
}
proc[NPROC-1].next = 0;
freeList = &proc[0]; // all PROCs in freeList
readyQueue = 0;
sleepList = 0;
// create P0 as initial running task
running = p = dequeue(&freeList);
p->status = READY;
p->priority = 0;
printList("freeList", freeList);
printf("init complete: P0 running\n");
}
int texit(int value)
{
printf("task %d in texit value=%d\n", running->pid, running->pid);
running->status = FREE;
running->priority = 0;
enqueue(&freeList, running);
printList("freeList", freeList);
tswitch();
}
int do_create()
{
int pid = create(func, running->pid); // parm = pid
}
int do_switch()
{
tswitch();
}
int do_exit()
{
texit(running->pid); // for simplicity: exit with pid value?
}
void func(void *parm)
{
int c;
printf("task %d start: parm = %d\n", running->pid, (int)parm);
while(1)
{
printf("task %d running\n", running->pid);
printList("readyQueue", readyQueue);
printf("enter a key [c|s|q]:");
c = getchar();
getchar();
switch (c)
{
case 'c' : do_create(); break;
case 's' : do_switch(); break;
case 'q' : do_exit(); break;
}
}
}
int create(void (*f)(), void *parm)
{
int i;
PROC *p = dequeue(&freeList);
if (!p)
{
printf("create failed\n");
return -1;
}
p->status = READY;
p->priority = 1;
p->joinPid = 0;
p->joinPtr = 0;
// initialize new task stack for it to resume
for (i=1; i<13; i++)
{
p->stack[SSIZE-i] = 0;
}
p->stack[SSIZE-1] = (int) parm;
p->stack[SSIZE-2] = (int)do_exit;
p->stack[SSIZE-3] = (int)f;
p->ksp = (int)&p->stack[SSIZE-12];
enqueue(&readyQueue, p);
printList("readyQueue", readyQueue);
printf("task %d created a new task %d\n", running->pid, p->pid);
return p->pid;
}
int main()
{
printf("Welcome to the MT User-Level Threads System\n");
init();
create((void *)func, 0);
printf("P0 switch to Pl\n");
while(1)
{
if (readyQueue)
tswitch();
}
}
int scheduler()
{
if (running->status == READY)
enqueue(&readyQueue, running);
running = dequeue(&readyQueue);
printf("next running = %d\n", running->pid);
}
运行截图: