任务1:
要求:编写程序task61.c,主线程创建3个对等线程T1、T2、T3,每个线程利用循环执行5次printf输出操作,两次循环间随机等待1-5s时间。主线程等待所有对等线程结束后终止进程。各对等线程的输出操作是:
- T1:输出“My name is <您的姓名xxx>”
- T2:输出“My student number is <您的学号xxx>”
- T3:输出“Current time <当前时间,包括年月日时分秒>
要求:
- 采用文件复制、文本复制或输入方式在Linux工作目录下创建源程序文件。
- 编译、调试、运行程序,观察输出结果
设计思想:
用随机数函数rand()来生产1-5之间的随机数,用时间结构体与时间格式函数strftime来记录当前时间与规定时间格式。
#include "wrapper.h"
void *C1(void *no);
void *C2(void *no);
void *C3(void *no);
int main()
{
pthread_t T1, T2, T3;
pthread_create(&T1, NULL, C1, NULL);
pthread_create(&T2, NULL, C2, NULL);
pthread_create(&T3, NULL, C3, NULL);
pthread_join(T1, NULL);
pthread_join(T2, NULL);
pthread_join(T3, NULL);
}
void *C1(void *no)
{
int i = 0;
while (i < 5)
{
printf("My name is 泠霖凛\n");
sleep(rand() % 5 + 1);
i++;
}
}
void *C2(void *no)
{
int i = 0;
while (i < 5)
{
printf("My student number is 201944101126\n");
sleep(rand() % 5 + 1);
i++;
}
}
void *C3(void *no)
{
time_t t;
struct tm *newtime;
char str[100];
int i = 0;
while (i < 5)
{
t = time(NULL);
newtime = localtime(&t);
strftime(str, 100, "<%Y年%m月%d日%H时%M分%S秒>\n", newtime);
printf("Current time%s", str);
sleep(rand() % 5 + 1);
i++;
}
}
任务2:
要求:编译、测试和运行图6-13示例程序badcount.c,请通过测试找到程序运行开始出错的niters的最小值,并解释出错原因。用pthread信号量方法改写程序badcount.c,保存为task62.c,实现对共享变量的安全访问,并进行测试验证。
要求:
- 用niters=10n进行测试,给出程序运行出错与n的关系,解释出错原因;
- 采用文件复制、文本复制或输入方式在Linux工作目录下创建源程序文件task62.c。
- 编译、调试、运行程序,观察输出结果
#include "wrapper.h"
void *increase(void *arg);
void *decrease(void *arg);
sem_t in;
int cnt = 0;
int main(int argc, char **argv)
{
unsigned int niters;
pthread_t tid1, tid2;
if (argc != 2)
{
printf("usage:%s <niters>\n", argv[0]);
exit(2);
}
niters = atoll(argv[1]);
sem_init(&in, 0, 1);
pthread_create(&tid1, NULL, increase, (void *)niters);
pthread_create(&tid2, NULL, decrease, (void *)niters);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
if (cnt != 0)
printf("Error! cnt=%d\n", cnt);
else
printf("Correct cnt=%d\n", cnt);
exit(0);
}
void *increase(void *vargp)
{
unsigned int i, niters = (unsigned int)vargp;
for (i = 0; i < niters; i++)
{
sem_wait(&in);
cnt++;
sem_post(&in);
}
return NULL;
}
void *decrease(void *vargp)
{
unsigned int i, niters = (unsigned int)vargp;
for (i = 0; i < niters; i++)
{
sem_wait(&in);
cnt--;
sem_post(&in);
}
return NULL;
}
任务3:
要求:编写一个多线程程序task63.c,创建k个生产者线程和m个消费者线程,每个生产者线程产生若干个随机数,通过由N个单元构成的缓冲区,发送给消费者线程,进行输出显示,产生Pthread信号量实现生产者/消费者线程间同步,并且设计一种方案对程序正确性进行验证。
提示:一种非严谨的简单验证方案是,将生产者线程产生的所有随机数相加得到一个和,消费者线程接收到的所有随机数相加得到另一个和,验证两个和是否一致来来验证程序正确性。
设计思想:
通过rand()函数生成范围在1-10的随机数。当消费者获取所有生产者生产的数后,通过pthread_cancel函数杀死剩余的消费者,然后检验生产的数和消费的数是否相同。
#include "wrapper.h"
#define K 10
#define M 20
#define N 20
int consum = 0;
int prosum = 0;
int buf[N], n, flag = K;
void *consumer(void *no);
void *producer(void *no);
sem_t avail, ready;
int main()
{
pthread_t con[K], pro[M];
sem_init(&avail, 0, 1);
sem_init(&ready, 0, 0);
for (int i = 0; i < K; i++)
pthread_create(&con[i], NULL, consumer, NULL);
for (int i = 0; i < M; i++)
pthread_create(&pro[i], NULL, producer, &i);
for (int i = 0; i < K; i++)
pthread_join(con[i], NULL);
while (1)
{
if (flag == 0)
{
for (int i = 0; i < M; i++)
pthread_cancel(pro[i]);
break;
}
}
sem_destroy(&avail);
sem_destroy(&ready);
printf("生产者生产了%d\n", consum);
printf("消费者消费了%d\n", prosum);
if (consum == prosum)
printf("正确!\n");
else
printf("错误!\n");
}
void *consumer(void *no)
{
sem_wait(&avail);
int num;
n = 0;
int sum = rand() % 20 + 1;
for (int i = 0; i < sum; i++)
{
num = rand() % 10 + 1;
consum += num;
buf[n++] = num;
}
sem_post(&ready);
}
void *producer(void *no)
{
sem_wait(&ready);
int id = *((int *)no);
printf("%d号消费者:", n);
for (int i = 0; i < n; i++)
{
printf("%d ", buf[i]);
prosum += buf[i];
}
printf("\n");
flag--;
sem_post(&avail);
}
任务4:
要求:编译、测试和运行示例程序psum64.c
- 测量线程数为1、2、4、8、16时程序的执行时间,计算加速比和效率,并做出解释。
线程(t) | 1 | 2 | 4 | 8 | 16 |
核(p) | 1 | 2 | 4 | 4 | 4 |
运行时间(Tp) | 2.51 | 1.28 | 0.64 | 0.47 | 0.46 |
加速比(Sp) | 1 | 1.96 | 3.94 | 5.34 | 5.46 |
效率(Ep) | 100% | 98% | 98.5% | 66.8% | 34.1% |
随着线程数的增加,运行时间减少。增加到4个线程后,运行时间趋于平稳。但增加到16个线程后,运行时间反而呈现增加的趋势,这是由于多个线程被分配到同一个核上,从而增加了线程上下文切换的开销。
2)改写该程序psum64.c,保存为task64.c,实现计算02+12+… +(n-1)2功能。
要求:
计算不同线程数时的性能,填写以下表格,并对运行时间和加速比进行解释:
线程(t) | 1 | 2 | 4 | 8 | 16 |
核(p) | 1 | 2 | 4 | 4 | 4 |
运行时间(Tp) | 0.016 | 0.011 | 0.007 | 0.007 | 0.008 |
加速比(Sp) | 1 | 1.45 | 2.29 | 2.29 | 2 |
效率(Ep) | 100% | 72.5% | 57.25% | 28.62% | 12.5% |
#include "wrapper.h"
#define MAXTHREADS 32
void *sum(void *vargp);
unsigned long long psum64[MAXTHREADS];
unsigned long long nelems_per_thread;
int main(int argc, char **argv)
{
unsigned long long i, nelems, log_nelems, nthreads, result = 0;
pthread_t tid[MAXTHREADS];
int myid[MAXTHREADS];
if (argc != 3)
{
printf("Usage:%s<ntheads><log_nelems>\n", argv[0]);
exit(0);
}
nthreads = atoi(argv[1]);
log_nelems = atoi(argv[2]);
nelems = (1LL << log_nelems);
nelems_per_thread = nelems / nthreads;
for (i = 0; i < nthreads; i++)
{
myid[i] = i;
pthread_create(&tid[i], NULL, sum, &myid[i]);
}
for (i = 0; i < nthreads; i++)
pthread_join(tid[i], NULL);
for (i = 0; i < nthreads; i++)
result += psum64[i];
if (result == (nelems * (nelems - 1) * (2 * nelems - 1) / 6))
printf("Corrrct:reasult=%ld\n", result);
else
printf("Error:result=%ld\n", result);
exit(0);
}
void *sum(void *vargp)
{
int myid = *((int *)vargp);
unsigned long long begin = myid * nelems_per_thread;
unsigned long long end = begin + nelems_per_thread;
unsigned long long i, lsum = 0;
for (i = begin; i < end; i++)
lsum += i * i;
psum64[myid] = lsum;
return NULL;
}
任务5:
要求:编写一个N×N矩阵乘法函数的并行线程化版本,程序保存为matmult.c,设计一种方案,验证并行程序正确性。编译、调试、运行程序,设2k≤N<2k+1给出线程数为1、2、4、…、2k-1、2k等情况下的运行时间,计算加速比与效率,并对结果给出解释。
线程数(t) | 1 | 2 | 4 | … | 2k-1 | 2k |
核(p) | 1 | 2 | 4 | 4 | 4 | |
运行时间(Tp) | 0.46 | 0.35 | 0.31 | 0.29 | 0.29 | |
加速比(Sp) | 1 | 1.31 | 1.48 | 1.59 | 1.59 | |
效率(Ep) | 100% | 65.5% | 37% | 1.59/2k-1*100% | 1.59/2k*100% |
提示:可将输出结果与正确串行版本计算结果进行对比来验证程序正确性
设计思想:通过线程的序号来确定当前线程所需计算的范围,用len来确定每个线程所需计算的量
#include "wrapper.h"
#define N 5000
int A1[N][N];
int A2[N][N];
unsigned long long len;
void *sum(void *no);
int main(int argc, int **argv)
{
unsigned long long i, j, ph = atoi(argv[1]);
pthread_t co[ph];
len = N / ph;
for (i = 0; i < N; i++)
for (int j = 0; j < N; j++)
A1[i][j] = rand() % 10 + 1;
for (i = 0; i < ph; i++)
pthread_create(&co[i], NULL, sum, &i);
for (i = 0; i < ph; i++)
pthread_join(co[i], NULL);
}
void *sum(void *no)
{
unsigned long long i, j, pd = *((int *)no);
for (i = pd * len; i < pd * len + len; i++)
for (j = 0; j < N; j++)
A2[i][j] = A1[i][j] * A1[j][i];
return NULL;
}
任务6:
要求:在Linux环境下,可以调用库函数gettimeday测量一个代码段的执行时间,精度可达微秒级,请写一个程序task66.c,测量和比较fork、pthread_create函数调用所需的执行时间,并进行解释。
提示:可以多次反复调用读写函数,计算累积时间
设计思想:使用gettimeofday函数得到当前时间
#include "wrapper.h"
void *cr(void *vargp) {}
int main()
{
pid_t p[100];
pthread_t t[100];
struct timeval tp;
struct timeval tp1;
double g_time_start;
double g_time_end;
gettimeofday(&tp, NULL);
g_time_start = tp.tv_sec * 1000000 + tp.tv_usec;
for (int i = 0; i < 100; i++)
pthread_create(&t[i], NULL, cr, NULL);
gettimeofday(&tp1, NULL);
g_time_end = tp1.tv_sec * 1000000 + tp1.tv_usec;
printf("pthread_create函数的运行时间为:%.4f微秒\n", (g_time_end - g_time_start) / 100);
gettimeofday(&tp, NULL);
g_time_start = tp.tv_sec * 1000000 + tp.tv_usec;
for (int i = 0; i < 100; i++)
if ((p[i] = fork()) == 0)
exit(0);
gettimeofday(&tp1, NULL);
g_time_end = tp1.tv_sec * 1000000 + tp1.tv_usec;
printf("fork函数的运行时间为:%.4f微秒\n", (g_time_end - g_time_start) / 100);
for (int i = 0; i < 100; i++)
{
waitpid(p[i], NULL, WNOHANG);
pthread_join(t[i], NULL);
}
exit(0);
}