多线程编程实践
1. 代码设计逻辑
1.1 功能要求
学习多线程编程实现以下功能:
1. 主进程从命令行接受三个参数 pa,pb,pc(pa>pb>pc)。其中 pa 表示线程 A 产生的有序数组的长度,pb 表示线程 B 的除数,pc 表示线程 C 的除数。
2. 生成并运行四个线程,各线程的工作如下: 线程 A 每隔 15 毫秒按序产生一个数(从 1 到 pa),把它们放到长度不超过 20 的 buffer 数组中。如果 buffer 已满,则需要等待空闲时再插入; 线程B每隔20毫秒查询一次buffer,统计所有能被 整除的数字的个数并求和; 线程C每隔15毫秒查询一次buffer,统计所有能被整除的数字的个数并求和; 线程 D 每隔 10 毫秒查询一次 buffer,统计所有其他数字的个数并求和;
1.2 逻辑设计
详细代码及注释请见 main.c,此处只展示大体思路。
1. 代码定义了一个缓冲区和计数器变量,以及三个求和变量和计数变量。缓冲区大小定义为 20,计数器变量 count 用于跟踪缓冲区中的元素数量。求和变 量分别为 sumB、sumC 和 sumD,用于保存过滤后的数字的总和。计数变量分别为 countB、countC 和 countD,用于跟踪过滤后的数字的数量。
2. 在本段代码中,互斥锁和条件变量的作用是同步线程之间对缓冲区的访问 和修改。当线程 A 向缓冲区添加数字时,它需要先获取互斥锁,以防止其他线 程同时修改缓冲区。当缓冲区已满时,线程 A 会等待条件变量,直到其他线程 从缓冲区中移除数字,腾出空间。一旦有空间可用,线程 A 会将数字添加到缓 冲区中,并广播条件变量,以通知其他线程缓冲区中有新的数字可用。 线程 BC 和线程 D 对缓冲区的访问和修改也需要使用互斥锁和条件变量来保证 线程之间的同步。当缓冲区为空时,线程 BC 和线程 D 会等待条件变量,直到 其他线程向缓冲区中添加数字。一旦有数字可用,它们会遍历缓冲区中的所有 数字,并对它们进行过滤和累加操作。当线程遇到终止信号(-1)时,它会广播 条件变量并解锁互斥锁,以通知其他线程退出线程函数。
3. 代码定义了三个线程函数:threadA、threadBC 和 threadD。这些线程函数将 在后面的代码中被创建和使用。
4. 代码的主函数 main 首先检查命令行参数,如果不是三个整数,则打印错误 消息并退出程序。然后,将命令行参数转换为整数,并将除数存储在数组中。 接着,代码创建了三个线程,并等待它们完成。最后,代码打印了过滤后的数 字的总和和数量。
5. 线程函数 threadA 负责生成一系列数字,并将它们添加到缓冲区中。对于每 个数字,线程函数首先休眠 15 毫秒,然后加锁,检查缓冲区是否已满。如果 缓冲区已满,则线程等待条件变量,直到缓冲区有空间可用。一旦有空间可用, 线程将数字添加到缓冲区中,增加计数器变量 count 的值。最后,线程广播条 件变量,并解锁互斥锁。如果线程已经生成了所有数字,则添加一个终止信号 (-1),并将其添加到缓冲区中。线程函数返回 NULL。
6. 线程函数 threadBC 和 threadD 负责过滤和累加缓冲区中的数字。这两个线 程的实现方式类似,只是过滤条件稍有不同。线程函数首先休眠一段时间(20 毫秒或 10 毫秒),然后加锁,检查缓冲区是否为空。如果缓冲区为空,则线程 等待条件变量,直到缓冲区中有数字可用。一旦有数字可用,线程遍历缓冲区 中的所有数字,并对它们进行过滤和累加操作。如果数字可以被给定的除数整 除,则将其累加到相应的求和变量中,并增加相应的计数变量值。最后,线程 从缓冲区中删除已处理的数字,并减少计数器变量 count 的值。如果遇到终止 信号(-1),则线程广播条件变量,并解锁互斥锁,然后返回 NULL。
7. 最后,主函数销毁互斥锁和条件变量,并打印过滤后的数字的总和和数量。
2. 测试示例
提供以下五组测试用例的执行结果:
./main 10 5 3
./main 100 5 3
./main 200 5 3
./main 200 10 5
./main 200 10 3
3. 代码展示
// 包含所需的库 #include <stdio.h> #include <pthread.h> #include <unistd.h> #include <stdlib.h> // 设置缓冲区大小 #define BUFFER_SIZE 20 // 声明缓冲区、计数器和求和变量 int buffer[BUFFER_SIZE]; int count = 0; int sumB = 0, sumC = 0, sumD = 0; int countB = 0, countC = 0, countD = 0; // 声明互斥锁和条件变量 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // 声明线程函数 void *threadA(void *arg); void *threadBC(void *arg); void *threadD(void *arg); int main(int argc, char *argv[]) { // 检查命令行参数 if (argc != 4) { fprintf(stderr, "usage: main <pa> <pb> <pc>\n"); return 1; } // 转换命令行参数为整数 int pa = atoi(argv[1]); int pb = atoi(argv[2]); int pc = atoi(argv[3]); // 将除数存储在数组中 int divisors[] = {pb, pc}; // 声明线程标识符 pthread_t ta, tbc, td; // 创建线程 pthread_create(&ta, NULL, threadA, &pa); pthread_create(&tbc, NULL, threadBC, divisors); pthread_create(&td, NULL, threadD, divisors); // 等待线程完成 pthread_join(ta, NULL); pthread_join(tbc, NULL); pthread_join(td, NULL); // 销毁互斥锁和条件变量 pthread_mutex_destroy(&mutex); pthread_cond_destroy(&cond); // 打印结果 printf("Thread B, %d, %d\n", countB, sumB); printf("Thread C, %d, %d\n", countC, sumC); printf("Thread D, %d, %d\n", countD, sumD); return 0; } // 线程A负责生成一系列数值 void *threadA(void *arg) { int pa = *((int *)arg); for (int i = 1; i <= pa; i++) { // 休眠15毫秒 usleep(15 * 1000); // 加锁,并在缓冲区满时等待 pthread_mutex_lock(&mutex); while (count >= BUFFER_SIZE) { pthread_cond_wait(&cond, &mutex); } // 向缓冲区添加数字 buffer[count] = i; count++; // 广播条件变量并解锁 pthread_cond_broadcast(&cond); pthread_mutex_unlock(&mutex); } // 添加终止信号 pthread_mutex_lock(&mutex); while (count >= BUFFER_SIZE) { pthread_cond_wait(&cond, &mutex); } buffer[count] = -1; count++; pthread_cond_broadcast(&cond); pthread_mutex_unlock(&mutex); return NULL; } // 线程B和C负责过滤并累加可以被给定的两个除数整除的数值 void *threadBC(void *arg) { int *divisors = (int *)arg; int pb = divisors[0]; int pc = divisors[1]; while (1) { // 休眠20毫秒 usleep(20 * 1000); // 加锁,并在缓冲区为空时等待 pthread_mutex_lock(&mutex); while (count == 0) { pthread_cond_wait(&cond, &mutex); } // 遍历缓冲区,过滤并累加可以被给定的两个除数整除的数值 int i = 0; while (i < count) { if (buffer[i] == -1) { // 如果遇到终止信号,广播条件变量并解锁 pthread_cond_broadcast(&cond); pthread_mutex_unlock(&mutex); return NULL; } if (buffer[i] % pb == 0) { // 如果可以被pb整除,累加到sumB sumB += buffer[i]; countB++; if (buffer[i] % pc == 0 && buffer[i] % pb == 0) { // 如果同时可以被pc整除,累加到sumC sumC += buffer[i]; countC++; } // 从缓冲区中移除已处理的数值 buffer[i] = buffer[count - 1]; count--; } else if (buffer[i] % pc == 0) { // 如果可以被pc整除,累加到sumC sumC += buffer[i]; countC++; // 从缓冲区中移除已处理的数值 buffer[i] = buffer[count - 1]; count--; } else { i++; } } // 广播条件变量并解锁 pthread_cond_broadcast(&cond); pthread_mutex_unlock(&mutex); } } // 线程D负责累加剩余的数值 void *threadD(void *arg) { int *divisors = (int *)arg; int pb = divisors[0]; int pc = divisors[1]; while (1) { // 休眠10毫秒 usleep(10 * 1000); // 加锁,并在缓冲区为空时等待 pthread_mutex_lock(&mutex); while (count == 0) { pthread_cond_wait(&cond, &mutex); } // 遍历缓冲区,累加剩余的数值 int i = 0; while (i < count) { if (buffer[i] == -1) { // 如果遇到终止信号,广播条件变量并解锁 pthread_cond_broadcast(&cond); pthread_mutex_unlock(&mutex); return NULL; } if (buffer[i] % pb != 0 && buffer[i] % pc != 0) { // 如果不能被pb或pc整除,累加到sumD sumD += buffer[i]; countD++; // 从缓冲区中移除已处理的数值 buffer[i] = buffer[count - 1]; count--; } else { i++; } } // 发送条件变量信号并解锁 pthread_cond_signal(&cond); pthread_mutex_unlock(&mutex); } }