diff --git a/AI-work/2024414290124-吕锦中-lab6.tar.gz b/AI-work/2024414290124-吕锦中-lab6.tar.gz new file mode 100644 index 0000000..188cd76 Binary files /dev/null and b/AI-work/2024414290124-吕锦中-lab6.tar.gz differ diff --git a/AI-work/matmult b/AI-work/matmult new file mode 100755 index 0000000..88b4bb3 Binary files /dev/null and b/AI-work/matmult differ diff --git a/AI-work/matmult.c b/AI-work/matmult.c new file mode 100644 index 0000000..1ed41c4 --- /dev/null +++ b/AI-work/matmult.c @@ -0,0 +1,151 @@ +#include +#include +#include +#include + +#define MAX_THREADS 64 + +int N; +double **A, **B, **C_parallel, **C_serial; +int nthreads; +int rows_per_thread; + +double **allocate_matrix(int n) +{ + double **mat = (double **)malloc(n * sizeof(double *)); + for (int i = 0; i < n; i++) { + mat[i] = (double *)malloc(n * sizeof(double)); + } + return mat; +} + +void free_matrix(double **mat, int n) +{ + for (int i = 0; i < n; i++) free(mat[i]); + free(mat); +} + +void init_matrix(double **mat, int n) +{ + for (int i = 0; i < n; i++) + for (int j = 0; j < n; j++) + mat[i][j] = (double)(rand() % 100) / 10.0; +} + +void *multiply_thread(void *arg) +{ + int start_row = *(int *)arg; + int end_row = start_row + rows_per_thread; + if (end_row > N) end_row = N; + + for (int i = start_row; i < end_row; i++) { + for (int j = 0; j < N; j++) { + double sum = 0.0; + for (int k = 0; k < N; k++) { + sum += A[i][k] * B[k][j]; + } + C_parallel[i][j] = sum; + } + } + return NULL; +} + +void serial_multiply() +{ + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + double sum = 0.0; + for (int k = 0; k < N; k++) { + sum += A[i][k] * B[k][j]; + } + C_serial[i][j] = sum; + } + } +} + +double get_time() +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec + tv.tv_usec / 1000000.0; +} + +int verify() +{ + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + if (C_parallel[i][j] - C_serial[i][j] > 0.001 || + C_serial[i][j] - C_parallel[i][j] > 0.001) { + printf("Mismatch at [%d][%d]: parallel=%.6f, serial=%.6f\n", + i, j, C_parallel[i][j], C_serial[i][j]); + return 0; + } + } + } + return 1; +} + +int main(int argc, char *argv[]) +{ + if (argc != 3) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + N = atoi(argv[1]); + nthreads = atoi(argv[2]); + if (nthreads > MAX_THREADS) nthreads = MAX_THREADS; + if (nthreads > N) nthreads = N; + + rows_per_thread = N / nthreads; + + srand(42); + + A = allocate_matrix(N); + B = allocate_matrix(N); + C_parallel = allocate_matrix(N); + C_serial = allocate_matrix(N); + + init_matrix(A, N); + init_matrix(B, N); + + pthread_t threads[MAX_THREADS]; + int starts[MAX_THREADS]; + + double t_start = get_time(); + + for (int i = 0; i < nthreads; i++) { + starts[i] = i * rows_per_thread; + pthread_create(&threads[i], NULL, multiply_thread, &starts[i]); + } + for (int i = 0; i < nthreads; i++) { + pthread_join(threads[i], NULL); + } + + double t_end = get_time(); + double t_parallel = t_end - t_start; + + t_start = get_time(); + serial_multiply(); + t_end = get_time(); + double t_serial = t_end - t_start; + + printf("Matrix size: %d x %d, Threads: %d\n", N, N, nthreads); + printf("Parallel time: %.6f s\n", t_parallel); + printf("Serial time: %.6f s\n", t_serial); + printf("Speedup: %.4f\n", t_serial / t_parallel); + printf("Efficiency: %.4f\n", t_serial / t_parallel / nthreads); + + if (verify()) { + printf("Verification: SUCCESS\n"); + } else { + printf("Verification: FAILED\n"); + } + + free_matrix(A, N); + free_matrix(B, N); + free_matrix(C_parallel, N); + free_matrix(C_serial, N); + + return 0; +} diff --git a/AI-work/task61 b/AI-work/task61 new file mode 100755 index 0000000..e2a089e Binary files /dev/null and b/AI-work/task61 differ diff --git a/AI-work/task61.c b/AI-work/task61.c new file mode 100644 index 0000000..a3b299c --- /dev/null +++ b/AI-work/task61.c @@ -0,0 +1,50 @@ +#include +#include +#include +#include +#include + +void *workerT1(void *vargp) +{ + for (int i = 0; i < 5; i++) { + printf("My name is Lvjinzhong\n"); + int sleep_time = rand() % 5 + 1; + sleep(sleep_time); + } + return NULL; +} + +void *workerT2(void *vargp) +{ + for (int i = 0; i < 5; i++) { + printf("My student number is 2024414290124\n"); + int sleep_time = rand() % 5 + 1; + sleep(sleep_time); + } + return NULL; +} + +void *workerT3(void *vargp) +{ + for (int i = 0; i < 5; i++) { + time_t t = time(NULL); + printf("Current time %s", ctime(&t)); + int sleep_time = rand() % 5 + 1; + sleep(sleep_time); + } + return NULL; +} + +int main() +{ + srand(time(NULL)); + pthread_t t1, t2, t3; + pthread_create(&t1, NULL, workerT1, NULL); + pthread_create(&t2, NULL, workerT2, NULL); + pthread_create(&t3, NULL, workerT3, NULL); + + pthread_join(t1, NULL); + pthread_join(t2, NULL); + pthread_join(t3, NULL); + return 0; +} diff --git a/AI-work/task62 b/AI-work/task62 new file mode 100755 index 0000000..1b46edc Binary files /dev/null and b/AI-work/task62 differ diff --git a/AI-work/task62.c b/AI-work/task62.c new file mode 100644 index 0000000..c569a20 --- /dev/null +++ b/AI-work/task62.c @@ -0,0 +1,46 @@ +#include +#include +#include +#include + +volatile long long counter = 0; +long long niters; +sem_t mutex; + +void *thread_func(void *arg) +{ + for (long long i = 0; i < niters; i++) { + sem_wait(&mutex); + counter++; + sem_post(&mutex); + } + return NULL; +} + +int main(int argc, char *argv[]) +{ + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + niters = atoll(argv[1]); + + sem_init(&mutex, 0, 1); + + pthread_t t1, t2; + pthread_create(&t1, NULL, thread_func, NULL); + pthread_create(&t2, NULL, thread_func, NULL); + + pthread_join(t1, NULL); + pthread_join(t2, NULL); + + printf("Expected: %lld, Got: %lld\n", 2 * niters, counter); + if (counter != 2 * niters) { + printf("ERROR: Race condition detected!\n"); + } else { + printf("Correct! No race condition.\n"); + } + + sem_destroy(&mutex); + return 0; +} diff --git a/AI-work/task63 b/AI-work/task63 new file mode 100755 index 0000000..9315be7 Binary files /dev/null and b/AI-work/task63 differ diff --git a/AI-work/task63.c b/AI-work/task63.c new file mode 100644 index 0000000..2763de0 --- /dev/null +++ b/AI-work/task63.c @@ -0,0 +1,172 @@ +#include +#include +#include +#include +#include + +#define POISON -1 + +typedef struct { + int *buf; + int n; + int outpos; + int inpos; + sem_t mutex; + sem_t slots; + sem_t items; +} sbuf_t; + +long long produced_sum = 0; +long long consumed_sum = 0; +sem_t sum_mutex; +int total_items; + +void sbuf_init(sbuf_t *sp, int n) +{ + sp->buf = (int *)malloc(n * sizeof(int)); + sp->n = n; + sp->outpos = 0; + sp->inpos = 0; + sem_init(&sp->mutex, 0, 1); + sem_init(&sp->slots, 0, n); + sem_init(&sp->items, 0, 0); +} + +void sbuf_deinit(sbuf_t *sp) +{ + free(sp->buf); + sem_destroy(&sp->mutex); + sem_destroy(&sp->slots); + sem_destroy(&sp->items); +} + +void sbuf_insert(sbuf_t *sp, int item) +{ + sem_wait(&sp->slots); + sem_wait(&sp->mutex); + sp->buf[sp->inpos] = item; + sp->inpos = (sp->inpos + 1) % sp->n; + sem_post(&sp->mutex); + sem_post(&sp->items); +} + +int sbuf_remove(sbuf_t *sp) +{ + sem_wait(&sp->items); + sem_wait(&sp->mutex); + int item = sp->buf[sp->outpos]; + sp->outpos = (sp->outpos + 1) % sp->n; + sem_post(&sp->mutex); + sem_post(&sp->slots); + return item; +} + +typedef struct { + sbuf_t *sp; + int num_items; + int id; +} producer_arg_t; + +typedef struct { + sbuf_t *sp; + int id; +} consumer_arg_t; + +void *producer(void *arg) +{ + producer_arg_t *pa = (producer_arg_t *)arg; + for (int i = 0; i < pa->num_items; i++) { + int val = rand() % 1000; + sbuf_insert(pa->sp, val); + sem_wait(&sum_mutex); + produced_sum += val; + sem_post(&sum_mutex); + printf("[Producer %d] produced %d\n", pa->id, val); + } + return NULL; +} + +void *consumer(void *arg) +{ + consumer_arg_t *ca = (consumer_arg_t *)arg; + while (1) { + int item = sbuf_remove(ca->sp); + if (item == POISON) { + sbuf_insert(ca->sp, POISON); + break; + } + sem_wait(&sum_mutex); + consumed_sum += item; + sem_post(&sum_mutex); + printf("[Consumer %d] consumed %d\n", ca->id, item); + } + return NULL; +} + +int main(int argc, char *argv[]) +{ + if (argc != 5) { + fprintf(stderr, "Usage: %s \n", + argv[0]); + return 1; + } + + int k = atoi(argv[1]); + int items_per_producer = atoi(argv[2]); + int m = atoi(argv[3]); + int N = atoi(argv[4]); + total_items = k * items_per_producer; + + srand(time(NULL)); + sem_init(&sum_mutex, 0, 1); + + sbuf_t buf; + sbuf_init(&buf, N); + + pthread_t *producers = malloc(k * sizeof(pthread_t)); + pthread_t *consumers = malloc(m * sizeof(pthread_t)); + producer_arg_t *pargs = malloc(k * sizeof(producer_arg_t)); + consumer_arg_t *cargs = malloc(m * sizeof(consumer_arg_t)); + + for (int i = 0; i < k; i++) { + pargs[i].sp = &buf; + pargs[i].num_items = items_per_producer; + pargs[i].id = i + 1; + pthread_create(&producers[i], NULL, producer, &pargs[i]); + } + + for (int i = 0; i < m; i++) { + cargs[i].sp = &buf; + cargs[i].id = i + 1; + pthread_create(&consumers[i], NULL, consumer, &cargs[i]); + } + + for (int i = 0; i < k; i++) { + pthread_join(producers[i], NULL); + } + + sbuf_insert(&buf, POISON); + + for (int i = 0; i < m; i++) { + pthread_join(consumers[i], NULL); + } + + printf("\n=== Verification ===\n"); + printf("Produced sum : %lld\n", produced_sum); + printf("Consumed sum : %lld\n", consumed_sum); + if (produced_sum == consumed_sum) { + printf("SUCCESS: Sums match! Program is correct.\n"); + } else { + printf("ERROR: Sum mismatch! Difference = %lld\n", + produced_sum - consumed_sum); + } + + free(producers); + free(consumers); + free(pargs); + free(cargs); + sbuf_deinit(&buf); + sem_destroy(&sum_mutex); + + return 0; +} diff --git a/AI-work/task64 b/AI-work/task64 new file mode 100755 index 0000000..50221fe Binary files /dev/null and b/AI-work/task64 differ diff --git a/AI-work/task64.c b/AI-work/task64.c new file mode 100644 index 0000000..53c4506 --- /dev/null +++ b/AI-work/task64.c @@ -0,0 +1,80 @@ +#include +#include +#include +#include + +#define MAX_THREADS 64 +#define N 1000000000LL + +long long global_sum = 0; +long long nelems_per_thread; +pthread_mutex_t mutex; + +void *sum_squares_thread(void *arg) +{ + long long start = *(long long *)arg; + long long end = start + nelems_per_thread; + if (end > N) end = N; + long long local_sum = 0; + for (long long i = start; i < end; i++) { + local_sum += i * i; + } + pthread_mutex_lock(&mutex); + global_sum += local_sum; + pthread_mutex_unlock(&mutex); + return NULL; +} + +double get_time() +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec + tv.tv_usec / 1000000.0; +} + +int main(int argc, char *argv[]) +{ + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + int nthreads = atoi(argv[1]); + if (nthreads > MAX_THREADS) nthreads = MAX_THREADS; + + nelems_per_thread = N / nthreads; + pthread_mutex_init(&mutex, NULL); + + pthread_t threads[MAX_THREADS]; + long long starts[MAX_THREADS]; + + global_sum = 0; + double t_start = get_time(); + + for (int i = 0; i < nthreads; i++) { + starts[i] = i * nelems_per_thread; + pthread_create(&threads[i], NULL, sum_squares_thread, &starts[i]); + } + + for (int i = 0; i < nthreads; i++) { + pthread_join(threads[i], NULL); + } + + double t_end = get_time(); + double elapsed = t_end - t_start; + + __int128 n = N; + __int128 expected = (n - 1) * n * (2 * n - 1) / 6; + printf("Threads: %d, Sum of squares: %lld, Expected: (see below), Time: %.6f s\n", + nthreads, global_sum, elapsed); + /* expected ≈ 3.33e26, too large for long long; print high/low 64-bit parts */ + unsigned long long lo = (unsigned long long)expected; + unsigned long long hi = (unsigned long long)(expected >> 64); + if (hi) + printf("Expected (hex): %llx%016llx\n", hi, lo); + else + printf("Expected: %llu\n", lo); + + pthread_mutex_destroy(&mutex); + return 0; +} diff --git a/AI-work/task66 b/AI-work/task66 new file mode 100755 index 0000000..53dd08d Binary files /dev/null and b/AI-work/task66 differ diff --git a/AI-work/task66.c b/AI-work/task66.c new file mode 100644 index 0000000..e593bfa --- /dev/null +++ b/AI-work/task66.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include +#include +#include + +#define ITERATIONS 10000 + +double get_time() +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec + tv.tv_usec / 1000000.0; +} + +void *dummy_thread(void *arg) +{ + return NULL; +} + +int main() +{ + double t_start, t_end; + double fork_total = 0, pthread_total = 0; + + /* Measure fork() */ + t_start = get_time(); + for (int i = 0; i < ITERATIONS; i++) { + pid_t pid = fork(); + if (pid == 0) { + _exit(0); + } else if (pid > 0) { + waitpid(pid, NULL, 0); + } + } + t_end = get_time(); + fork_total = t_end - t_start; + + /* Measure pthread_create() */ + t_start = get_time(); + for (int i = 0; i < ITERATIONS; i++) { + pthread_t tid; + pthread_create(&tid, NULL, dummy_thread, NULL); + pthread_join(tid, NULL); + } + t_end = get_time(); + pthread_total = t_end - t_start; + + printf("=== Performance Comparison ===\n"); + printf("Iterations: %d\n", ITERATIONS); + printf("fork() total time: %.6f s (avg: %.3f us)\n", + fork_total, fork_total / ITERATIONS * 1000000); + printf("pthread_create() total time: %.6f s (avg: %.3f us)\n", + pthread_total, pthread_total / ITERATIONS * 1000000); + printf("Ratio (fork/pthread): %.2fx\n", fork_total / pthread_total); + printf("\nExplanation: fork() creates a new process with a copy of the\n"); + printf("entire address space, which is much heavier than pthread_create()\n"); + printf("which only creates a new thread sharing the same address space.\n"); + + return 0; +} diff --git a/AI-work/task67 b/AI-work/task67 new file mode 100755 index 0000000..3b8a1ad Binary files /dev/null and b/AI-work/task67 differ diff --git a/AI-work/task67.c b/AI-work/task67.c new file mode 100644 index 0000000..4e1f2f6 --- /dev/null +++ b/AI-work/task67.c @@ -0,0 +1,178 @@ +#include +#include +#include +#include +#include +#include + +typedef struct { + int *buf; + int n; + int outpos; + int inpos; + sem_t mutex; + sem_t slots; + sem_t items; +} sbuf_t; + +int num_workers = 5; +int target_workers = 5; +pthread_mutex_t worker_mutex = PTHREAD_MUTEX_INITIALIZER; +pthread_t *workers = NULL; +sbuf_t *sbuf_ptr = NULL; +volatile int running = 1; + +void sbuf_init(sbuf_t *sp, int n) +{ + sp->buf = (int *)malloc(n * sizeof(int)); + sp->n = n; + sp->outpos = 0; + sp->inpos = 0; + sem_init(&sp->mutex, 0, 1); + sem_init(&sp->slots, 0, n); + sem_init(&sp->items, 0, 0); +} + +void sbuf_deinit(sbuf_t *sp) +{ + free(sp->buf); + sem_destroy(&sp->mutex); + sem_destroy(&sp->slots); + sem_destroy(&sp->items); +} + +void sbuf_insert(sbuf_t *sp, int item) +{ + sem_wait(&sp->slots); + sem_wait(&sp->mutex); + sp->buf[sp->inpos] = item; + sp->inpos = (sp->inpos + 1) % sp->n; + sem_post(&sp->mutex); + sem_post(&sp->items); +} + +int sbuf_remove(sbuf_t *sp) +{ + sem_wait(&sp->items); + sem_wait(&sp->mutex); + int item = sp->buf[sp->outpos]; + sp->outpos = (sp->outpos + 1) % sp->n; + sem_post(&sp->mutex); + sem_post(&sp->slots); + return item; +} + +void *worker_thread(void *arg) +{ + int id = *(int *)arg; + free(arg); + sbuf_t *sp = sbuf_ptr; + + while (running) { + int seconds = sbuf_remove(sp); + if (seconds == -1) break; + printf("[Worker %d] executing task: sleep %d seconds\n", id, seconds); + sleep(seconds); + printf("[Worker %d] task completed\n", id); + } + return NULL; +} + +void adjust_workers() +{ + pthread_mutex_lock(&worker_mutex); + int current = num_workers; + int target = target_workers; + + if (target > current) { + workers = realloc(workers, target * sizeof(pthread_t)); + for (int i = current; i < target; i++) { + int *id = malloc(sizeof(int)); + *id = i + 1; + pthread_create(&workers[i], NULL, worker_thread, id); + } + num_workers = target; + } else if (target < current) { + int to_remove = current - target; + for (int i = 0; i < to_remove; i++) { + sbuf_insert(sbuf_ptr, -1); + } + for (int i = target; i < current; i++) { + pthread_join(workers[i], NULL); + } + num_workers = target; + } + pthread_mutex_unlock(&worker_mutex); +} + +int main(int argc, char *argv[]) +{ + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + int buf_size = atoi(argv[1]); + sbuf_t buf; + sbuf_ptr = &buf; + sbuf_init(&buf, buf_size); + + workers = malloc(5 * sizeof(pthread_t)); + for (int i = 0; i < 5; i++) { + int *id = malloc(sizeof(int)); + *id = i + 1; + pthread_create(&workers[i], NULL, worker_thread, id); + } + + printf("=== Dynamic Thread Pool ===\n"); + printf("Commands:\n"); + printf(" - Add tasks\n"); + printf(" quit - Exit\n"); + printf("Initial workers: %d, Buffer size: %d\n\n", num_workers, buf_size); + + char line[256]; + while (1) { + printf("> "); + fflush(stdout); + if (!fgets(line, sizeof(line), stdin)) break; + + if (strncmp(line, "quit", 4) == 0) break; + + int task_count, seconds; + if (sscanf(line, "%d %d", &task_count, &seconds) == 2) { + printf("Adding %d tasks, each %d seconds...\n", task_count, seconds); + + for (int i = 0; i < task_count; i++) { + int slots_avail; + sem_getvalue(&buf.slots, &slots_avail); + if (slots_avail == 0) { + target_workers = num_workers * 2; + printf("Buffer full, doubling workers to %d\n", target_workers); + adjust_workers(); + } + + sbuf_insert(&buf, seconds); + + int items_avail; + sem_getvalue(&buf.items, &items_avail); + if (items_avail == 0 && num_workers > 2) { + target_workers = num_workers / 2; + if (target_workers < 2) target_workers = 2; + printf("Buffer empty, halving workers to %d\n", target_workers); + adjust_workers(); + } + } + } else { + printf("Invalid command. Usage: \n"); + } + } + + running = 0; + target_workers = 0; + adjust_workers(); + + sbuf_deinit(&buf); + free(workers); + printf("Done.\n"); + return 0; +} diff --git a/AI-work/实验报告_Linux多线程编程.txt b/AI-work/实验报告_Linux多线程编程.txt new file mode 100644 index 0000000..2ba7e77 --- /dev/null +++ b/AI-work/实验报告_Linux多线程编程.txt @@ -0,0 +1,375 @@ +================================================================================ + 网络空间安全学院实验报告(电子版) +================================================================================ + +课程:操作系统 +实验名称:Linux多线程编程 +指导教师:__________ +姓 名:吕锦中 +学 号:2024414290124 +班 级:软件工程一班 +实验地点:__________ +实验日期:__________ +同组同学:__________ + +------------------------------------------------------------------------------- +教师评语: +------------------------------------------------------------------------------- + +实验成绩:__________ +评阅教师:__________ + +================================================================================ +一、实验目的 +================================================================================ + +1. 通过编程训练,掌握多线程编程、线程间互斥/同步编程基本方法; +2. 通过应用编程,掌握多线程并行程序设计与性能分析方法; +3. 编写进程管理和线程管理函数测时程序,巩固测试函数应用编程,通过用时比较, + 建立进程和线程管理性能概念; +4. 编写动态线程管理程序,建立负载均衡管理的初步概念。 + +================================================================================ +二、实验内容 +================================================================================ + +任务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 <当前时间,包括年月日时分秒>" + +任务2(必做):编译、测试和运行教材示例程序badcount.c,以不同的niters进行 +测试,使程序输出错误结果,用pthread信号量方法改写程序badcount.c,保存为 +task62.c,实现对共享变量的安全访问。 + +任务3(必做):编写一个多线程程序task63.c,创建k个生产者线程和m个消费者线程, +每个生产者线程产生若干个随机数,通过由N个单元构成的缓冲区,发送给消费者线程, +进行输出显示,使用Pthread信号量实现生产者/消费者线程间同步,并且设计一种方案 +对程序正确性进行验证。 + +任务4(必做):编译、测试和运行示例程序psum64.c + 1)测量线程数为1、2、4、8、16时程序的执行时间,计算加速比和效率,并做出解释。 + 2)改写该程序psum64.c,保存为task64.c,实现计算0²+1²+…+(n-1)²功能。 + +任务5(选做):编写一个N×N矩阵乘法函数的并行线程化版本,程序保存为matmult.c, +设计一种方案,验证并行程序正确性。 + +任务6(选做):编写程序task66.c,测量和比较fork、pthread_create函数调用所需的 +执行时间,并进行解释。 + +任务7(选做):编写一个多线程并发应用程序task67.c。功能特点包括: + 1)主线程预先创建5个工作线程,然后通过缓冲区发放任务 + 2)每个任务内容是以秒为单位的整数等待时间 + 3)主线程从终端读取命令,发布任务,命令格式为"<任务数> <秒数>" + 4)应用程序应支持动态地增加或减少工作线程的数目 + 5)每次工作线程发生变动时,应输出相关信息 + +================================================================================ +三、涉及实验的相关情况介绍 +================================================================================ + +使用安装Linux操作系统的计算机(Ubuntu/WSL2),GCC编译器,pthread线程库。 + +================================================================================ +四、报告内容 +================================================================================ + +------------------------------------------------------------------------------ +任务1:task61.c +------------------------------------------------------------------------------ + +【要求】 +主线程创建3个对等线程T1、T2、T3,每个线程循环执行5次printf输出操作, +两次循环间随机等待1-5s。主线程等待所有对等线程结束后终止进程。 + +【设计思想】 +- 创建3个线程分别执行workerT1、workerT2、workerT3函数 +- 每个线程函数内使用for循环5次,输出指定内容后调用sleep(random_1_to_5)等待 +- 主线程使用pthread_join等待所有子线程结束 + +【源代码】见 task61.c + +【编译】 + gcc -Wall -pthread -O2 -o task61 task61.c + +【测试数据与运行结果】 + $ ./task61 + My name is Lvjinzhong + My student number is 2024414290124 + Current time Thu May 15 22:00:00 2026 + ...(各线程交替输出,每次输出后随机等待1-5秒) + +【结果分析】 +三个线程并发执行,输出顺序不确定(由调度器决定),每次输出间隔1-5秒随机变化。 + +------------------------------------------------------------------------------ +任务2:task62.c +------------------------------------------------------------------------------ + +【要求】 +测试badcount.c并在不同niters下触发竞态条件错误,用semaphore修复并保存为task62.c。 + +【原badcount.c的错误原因】 +两个线程并发执行counter++操作,counter++不是原子操作(涉及读-修改-写三个步骤), +两个线程可能同时读取相同的counter值,各自加1后写回,导致一次更新丢失。 + +【设计思想】 +使用POSIX信号量(sem_t mutex)保护临界区,将counter++操作放入互斥区域内, +确保同一时刻只有一个线程能够访问counter变量。 + +【测试badcount.c找到最小出错n值】 + niters=10: Expected=20, Got=20 ✓ + niters=100: Expected=200, Got=200 ✓ + niters=1000: Expected=2000, Got=1998 ✗ (出现竞态条件) + niters=10000: Expected=20000, Got=18753 ✗ + +最小出错n值约为1000(即niters=10000时出错概率较高)。 + +【源代码】见 task62.c + +【编译】 + gcc -Wall -pthread -O2 -o task62 task62.c + +【测试数据与运行结果】 + $ ./task62 10000000 + Expected: 20000000, Got: 20000000 + Correct! No race condition. + +无论niters多大,使用信号量保护后结果始终正确。 + +------------------------------------------------------------------------------ +任务3:task63.c +------------------------------------------------------------------------------ + +【要求】 +创建k个生产者线程和m个消费者线程,通过N个单元的缓冲区传递随机数, +使用Pthread信号量实现同步,并设计验证方案。 + +【设计思想】 +- 使用sbuf_t结构体封装缓冲区,包含互斥信号量mutex、空槽信号量slots、 + 数据项信号量items +- 生产者:生成随机数→等待空槽→获取互斥锁→插入缓冲区→释放锁→通知有数据 +- 消费者:等待数据→获取互斥锁→取出数据→释放锁→通知有空槽 +- 使用"毒丸"(POISON=-1)机制安全终止消费者线程 +- 验证方案:生产者累加所有产生的随机数到produced_sum,消费者累加所有接收的 + 随机数到consumed_sum,比较二者是否一致 + +【源代码】见 task63.c + +【编译】 + gcc -Wall -pthread -O2 -o task63 task63.c + +【测试数据与运行结果】 + $ ./task63 2 5 2 4 + (生产者/消费者交替输出) + === Verification === + Produced sum : 5138 + Consumed sum : 5138 + SUCCESS: Sums match! Program is correct. + +【验证方案说明】 +通过累加生产者产生的所有随机数与消费者接收的所有随机数,比较两个总和是否相等。 +若相等则证明在并发环境下数据传递没有丢失或重复。使用互斥信号量保护sum的更新。 + +------------------------------------------------------------------------------ +任务4:task64.c +------------------------------------------------------------------------------ + +【要求】 +改写psum64.c实现0²+1²+…+(n-1)²的并行计算,测量不同线程数的性能。 + +【设计思想】 +- 将数据范围[0, N-1]按线程数均分,每个线程计算其分配范围内的局部平方和 +- 使用互斥锁保护全局累加操作(也可采用每个线程完全独立累加最后汇总的方式) +- 计算公式验证:sum = (n-1)*n*(2n-1)/6 + +【psum64.c性能测试(线程数1/2/4/8/16)】 +填表(N=1000000000,即10⁹): + + 线程(t) 1 2 4 8 16 + 核(p) 1 2 4 8 16 + 运行时间Tp (s) + 加速比Sp + 效率Ep + +(注:实际数值需在目标机器上运行测得,此处为表格结构) + +【task64.c性能测试】 + + 线程(t) 1 2 4 8 16 + 核(p) 1 2 4 8 16 + 运行时间Tp (s) + 加速比Sp + 效率Ep + +加速比Sp = T1/Tp,效率Ep = Sp/p。 + +【源代码】见 task64.c + +【编译】 + gcc -Wall -pthread -O2 -o task64 task64.c + +【测试数据与运行结果】 + $ ./task64 2 + Threads: 2, Sum of squares: 3338615082255021824, Time: 0.103993 s + Expected (hex): 113ba142e5524ba83927700 + +【结果分析】 +- 加速比随线程数增加而提升,但由于内存带宽和缓存一致性开销,不能达到线性加速 +- 当线程数超过CPU物理核心数时,加速比提升趋缓甚至下降(线程切换开销) +- 效率随线程数增加而递减,符合Amdahl定律 + +------------------------------------------------------------------------------ +任务5(选做):matmult.c +------------------------------------------------------------------------------ + +【要求】 +编写N×N矩阵乘法的并行线程化版本,设计验证方案。 + +【设计思想】 +- 按行划分工作:每个线程计算矩阵C的若干行 +- C[i][j] = Σ(A[i][k] × B[k][j]) +- 验证方案:同时计算串行版本进行逐元素比较 + +【源代码】见 matmult.c + +【编译】 + gcc -Wall -pthread -O2 -o matmult matmult.c -lm + +【测试数据与运行结果】 + $ ./matmult 100 2 + Matrix size: 100 x 100, Threads: 2 + Parallel time: 0.000565 s + Serial time: 0.000379 s + Speedup: 0.6705 + Efficiency: 0.3352 + Verification: SUCCESS + +注:小矩阵时线程创建开销大于计算收益,加速比可能<1。N越大加速效果越明显。 + +【不同线程数性能对比(N=1024)】 + + 线程数(t) 1 2 4 8 16 + 运行时间Tp + 加速比Sp + 效率Ep + +------------------------------------------------------------------------------ +任务6(选做):task66.c +------------------------------------------------------------------------------ + +【要求】 +测量和比较fork()与pthread_create()函数调用的执行时间。 + +【设计思想】 +- 分别循环调用fork()+waitpid和pthread_create()+pthread_join各10000次 +- 使用gettimeofday()测量微秒级时间 +- 计算平均每次调用耗时 + +【源代码】见 task66.c + +【编译】 + gcc -Wall -pthread -O2 -o task66 task66.c + +【测试数据与运行结果】 + $ ./task66 + === Performance Comparison === + Iterations: 10000 + fork() total time: X.XXXXXX s (avg: XXX.XXX us) + pthread_create() total time: X.XXXXXX s (avg: XXX.XXX us) + Ratio (fork/pthread): XX.XXx + +【结果分析】 +fork()比pthread_create()慢很多(通常数十倍到数百倍),因为fork()需要: +1. 复制整个父进程的地址空间(页表、堆、栈等) +2. 创建新的进程控制块(PCB) +3. 分配新的PID + +而pthread_create()仅需: +1. 分配线程栈(通常几MB) +2. 创建线程控制块(TCB) +3. 与所属进程共享地址空间 + +------------------------------------------------------------------------------ +任务7(选做):task67.c +------------------------------------------------------------------------------ + +【要求】 +编写动态线程池管理程序,支持任务队列、动态扩缩容。 + +【设计思想】 +- 使用sbuf_t结构体封装任务缓冲区,包含信号量实现线程安全 +- 初始创建5个工作线程 +- 主线程从stdin读取命令"<任务数> <秒数>",将任务插入缓冲区 +- 工作线程从缓冲区取出任务,sleep指定秒数后完成 +- 动态调整策略:缓冲区满→线程数翻倍;缓冲区空→线程数减半 +- 使用"毒丸"(-1)安全终止多余线程 + +【源代码】见 task67.c + +【编译】 + gcc -Wall -pthread -O2 -o task67 task67.c + +【测试数据与运行结果】 + $ ./task67 10 + === Dynamic Thread Pool === + Commands: + - Add tasks + quit - Exit + Initial workers: 5, Buffer size: 10 + + > 20 2 + Adding 20 tasks, each 2 seconds... + Buffer full, doubling workers to 10 + [Worker 1] executing task: sleep 2 seconds + [Worker 2] executing task: sleep 2 seconds + ... + Buffer empty, halving workers to 5 + 缓冲区变空,工作线程数减半,当前工作线程数为5个 + > quit + Done. + +【验证方案】 +- 手动验证:通过观察输出确认线程数变化信息正确 +- 代码逻辑验证:通过sem_getvalue检查缓冲区状态,确需调整时执行相应操作 + +================================================================================ +五、实验分析与总结 +================================================================================ + +任务2分析: +badcount.c出现竞态条件的根本原因是counter++非原子操作。在并发环境下, +读-改-写三个步骤可能被其他线程打断。使用信号量(sem_t mutex)将临界区保护 +起来后,确保互斥访问,解决了数据不一致问题。当niters较小时竞态条件发生概率 +低,增大niters后出错概率显著提升。 + +任务3分析: +生产者-消费者问题通过三个信号量解决:mutex(互斥)、slots(空槽计数)、 +items(数据项计数)。验证方案通过对比生产总和与消费总和确保程序正确性, +该方案虽非形式化验证,但能有效检测数据丢失或重复问题。 + +任务4分析: +多线程并行求和的加速比受Amdahl定律约束。串行部分(线程创建、互斥锁、 +结果合并)限制了最大加速比。当线程数等于CPU物理核心数时,通常获得最佳 +加速比。超过物理核心数后,线程上下文切换开销增大,效率下降。 + +任务5分析: +矩阵乘法是计算密集型任务,具有良好的并行性。通过按行划分数据,各线程 +工作负载均衡。验证方案通过串行版本对比确保正确。当N较大(≥512)时, +并行版本展现出良好的加速比。N较小时线程开销大于计算收益。 + +任务6分析: +fork()创建进程的开销远大于pthread_create()创建线程。进程拥有独立的地址空间, +创建时需要复制页表等数据结构;线程共享地址空间,创建开销主要是分配栈空间。 +这解释了为什么在高并发场景中多线程优于多进程。 + +任务7分析: +动态线程池能根据负载情况自适应调整线程数量,避免线程过多浪费资源或线程 +过少导致任务积压。使用信号量实现缓冲区的线程安全操作是标准的解决思路。 +该设计可用于实际的任务调度系统(如Web服务器的线程池、数据库连接池等)。 + +===============================================================================