Tips:"分享是快乐的源泉💧,在我的博客里,不仅有知识的海洋🌊,还有满满的正能量加持💪,快来和我一起分享这份快乐吧😊!
喜欢我的博客的话,记得点个红心❤️和小关小注哦!您的支持是我创作的动力!数据源
存放在我的资源下载区啦!
Linux程序开发(九):进程及线程编程解决卡片游戏和时钟倒计时问题
目录
- Linux程序开发(九):进程及线程编程解决卡片游戏和时钟倒计时问题
-
- 1. 问答题
- 2. 编程题
-
- 2.1. 编写一个程序,写方式打开文件a.txt,开启四个线程,每个线程将自己的线程ID向文件中写入5遍,要求必须按顺序循环写入。如:abcdabcdabcdabcdabcd。
- 2.2. 如下有一个整型全局变量,编写双线程程序,一个线程递增该变量,另一个线程打印该变量,要保证按变量值递增顺序打印到屏幕,不重复,不跳跃。
- 2.3. 编写一个程序,创建三个线程,第一个线程屏幕打印0~9十个数字,第二个线程紧接着打印a~z二十六个小写字母,第三个线程最后再打印A~Z二十六个大写字母。
- 2.4. 下面的C程序homework4a.c,采用双线程搜索素数,主函数通过命令行参数接收最大搜索的整数,生成两个线程进行搜索。由于没有采用互斥算法,所以运行时有一些素数会被重复搜索(两个线程都搜索到),请采用互斥量修改代码,使搜索结果不重复。
- 2.5. 一堆数字卡片,从1到100。甲乙两人分拣卡片,按顺序分拣,每12张一打,甲负责按顺序挑出卡片,乙负责将12张卡片打包,最后不足12张打一个包。请采用双线程和条件变量、互斥量程序模拟以上过程。
- 2.6. 编写一个倒计时软件,主函数接收键盘输入的倒计时任务名(字符串)和倒计时时间(秒),创建线程将倒计时过程打印到屏幕,程序可以多次输入倒计时任务和时间。例如:
1. 问答题
1.1. 进程与线程的区别是什么?编程时如何选择?
进程(Process)和线程(Thread)是操作系统中的概念,用于实现并发执行的方式。它们之间有以下区别:
(1)资源占用:每个进程都有独立的地址空间、文件描述符等系统资源,进程之间相互隔离,相互之间不能直接访问对方的资源。而线程共享同一个进程的资源,包括地址空间、文件描述符等。
(2)切换开销:由于进程之间的隔离性,切换进程时需要切换地址空间和其他资源,切换开销较大。线程切换时只需切换线程的上下文环境,切换开销较小。
(3)通信与同步:不同进程之间的通信较为复杂,常见的通信方式有管道、共享内存、消息队列等。线程之间共享进程的资源,通信简单且直接。但线程之间共享资源容易引发同步问题,需要使用锁、条件变量等机制保证线程安全。
(4)可扩展性:多进程可以充分利用多核处理器的能力,提高程序的执行效率。多线程在单核处理器上执行时,由于线程切换开销较小,能够更充分利用CPU时间片,提高并发能力。
在选择使用进程还是线程时,需要根据具体的情况来决定:
(1)如果任务之间是密集计算型的,且需要充分利用多核处理器的能力,可以选择使用多进程。
(2)如果任务之间是I/O密集型的,涉及到网络通信、文件操作等,可以选择使用多线程,以避免I/O阻塞。
(3)如果需要在程序中实现复杂的同步和通信机制,或者需要对资源进行精细的控制,可以选择使用多线程。
1.2. 什么是同步和互斥?线程间有哪些同步互斥方式?
同步(Synchronization)和互斥(Mutual Exclusion)是并发编程中的两个重要概念。
一、同步指的是多个线程之间按照一定的顺序协调执行,以达到预期的结果。同步可以确保线程在特定时刻以特定的顺序执行,避免并发执行引发的问题。
二、互斥指的是对共享资源的访问进行限制,同一时间只允许一个线程访问共享资源,其他线程需要等待。互斥机制可以避免多个线程同时修改共享资源导致的数据不一致等问题。
线程间常用的同步和互斥方式包括:
(1)互斥锁(Mutex):也称为互斥量,是一种最常见的同步机制。通过使用互斥锁,只有获得锁的线程才能访问共享资源,其他线程需要等待锁的释放。
(2)信号量(Semaphore):是一种计数器,用于控制同时访问某个共享资源的线程数量。信号量可以设置初始值,并在线程访问共享资源前进行 P(wait)操作,访问结束后进行 V(signal)操作。
(3)条件变量(Condition):用于在线程之间建立先后关系,实现线程的等待和唤醒。条件变量结合互斥锁使用,线程可以通过条件变量等待某个条件满足,当条件满足时,其他线程可以通过条件变量进行唤醒。
(4)读写锁(Read-Write Lock):用于共享资源的读写操作。允许多个线程同时读取共享资源,但只允许一个线程进行写操作。这样可以提高读操作的并发性能。
(5)屏障(Barrier):用于控制线程的执行顺序,要求所有线程都到达屏障点后才能继续执行。常用于多线程任务的分阶段执行。
1.3. 什么是死锁?
死锁(Deadlock)是指在多线程或多进程的系统中,每个线程或进程都在等待一个只能由其他线程或进程释放的资源,导致所有线程或进程无法继续执行,陷入无限等待的状态。
死锁通常发生在多个线程或进程同时持有一些共享资源,并且每个线程或进程又需要获取其他线程或进程持有的资源才能继续执行。当每个线程或进程都在等待其他线程或进程释放资源时,就会导致死锁。
死锁的产生通常需要满足以下四个条件,被称为死锁的必要条件:
(1)互斥条件:资源不能被同时多个线程或进程占用,即资源只能被独占。
(2)请求与保持条件:线程或进程已经持有了至少一个资源,并且在请求其他资源。
(3)不剥夺条件:已经分配给线程或进程的资源不能被强制剥夺,只能由持有资源的线程或进程主动释放。
(4)循环等待条件:存在一个线程或进程的资源请求序列,使得每个线程或进程都在等待下一个线程或进程释放资源,形成一个循环等待的环路。
当以上四个条件同时满足时,就可能发生死锁。
死锁的解决办法:主要包括预防死锁、避免死锁和检测与解除死锁。
预防死锁:是通过破坏死锁的四个必要条件中的一个或多个来预防死锁的发生。
避免死锁:是在资源分配过程中,根据系统状态和资源请求情况,动态地决定是否分配资源,以避免发生死锁。
检测与解除死锁:是通过周期性地检测系统资源分配状态来判断是否发生死锁,并采取相应的措施解除死锁。
2. 编程题
2.1. 编写一个程序,写方式打开文件a.txt,开启四个线程,每个线程将自己的线程ID向文件中写入5遍,要求必须按顺序循环写入。如:abcdabcdabcdabcdabcd。
(1)在linux上编写c语言程序write_threads.c
#include <stdio.h>
#include <pthread.h>
#define THREAD_NUM 4
#define LOOP_NUM 5
pthread_mutex_t mutex;
FILE* file;
void* write_thread(void* arg) {
int thread_id = *((int*)arg);
for (int i = 0; i < LOOP_NUM; i++) {
pthread_mutex_lock(&mutex);
char c = 'a' + thread_id;
for (int j = 0; j < THREAD_NUM; j++) {
fputc(c, file);
}
pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main() {
file = fopen("a.txt", "w");
if (file == NULL) {
printf("Failed to open file.\n");
return 1;
}
pthread_t threads[THREAD_NUM];
int thread_ids[THREAD_NUM];
pthread_mutex_init(&mutex, NULL);
for (int i = 0; i < THREAD_NUM; i++) {
thread_ids[i] = i;
pthread_create(&threads[i], NULL, write_thread, &thread_ids[i]);
}
for (int i = 0; i < THREAD_NUM; i++) {
pthread_join(threads[i], NULL);
}
pthread_mutex_destroy(&mutex);
fclose(file);
return 0;
}
(2) 用gcc编译器进行程序编译
gcc -o write_threads write_threads.c -pthread -std=c99
(3) 运行程序
./write_threads
2.2. 如下有一个整型全局变量,编写双线程程序,一个线程递增该变量,另一个线程打印该变量,要保证按变量值递增顺序打印到屏幕,不重复,不跳跃。
int NN = 0;
打印效果:
NN=1
NN=2
…
(1)在linux上编写c语言程序increment_print.c
#include <stdio.h>
#include <pthread.h>
int NN = 0; // 全局变量 NN
pthread_mutex_t mutex; // 互斥锁
pthread_cond_t cond; // 条件变量
// 递增线程函数
void* increment_thread(void* arg) {
for (int i = 1; i <= 10; i++) {
pthread_mutex_lock(&mutex); // 上锁
while (i != NN + 1) {
pthread_cond_wait(&cond, &mutex); // 等待条件满足
}
NN++; // 递增 NN
printf("NN=%d\n", NN); // 打印当前值
pthread_cond_broadcast(&cond); // 广播通知其他等待的线程
pthread_mutex_unlock(&mutex); // 解锁
}
return NULL;
}
// 打印线程函数
void* print_thread(void* arg) {
for (int i = 1; i <= 10; i++) {
pthread_mutex_lock(&mutex); // 上锁
while (i != NN + 1) {
pthread_cond_wait(&cond, &mutex); // 等待条件满足
}
printf("NN=%d\n", NN); // 打印当前值
pthread_cond_broadcast(&cond); // 广播通知其他等待的线程
pthread_mutex_unlock(&mutex); // 解锁
}
return NULL;
}
int main() {
pthread_t increment_tid, print_tid; // 递增线程和打印线程的线程 ID
pthread_mutex_init(&mutex, NULL); // 初始化互斥锁
pthread_cond_init(&cond, NULL); // 初始化条件变量
pthread_create(&increment_tid, NULL, increment_thread, NULL); // 创建递增线程
pthread_create(&print_tid, NULL, print_thread, NULL); // 创建打印线程
pthread_join(increment_tid, NULL); // 等待递增线程结束
pthread_join(print_tid, NULL); // 等待打印线程结束
pthread_mutex_destroy(&mutex); // 销毁互斥锁
pthread_cond_destroy(&cond); // 销毁条件变量
return 0;
}
(2)用gcc编译器进行程序编译
gcc -pthread -std=c99 -o increment_print increment_print.c
(3)运行程序
./increment_print
2.3. 编写一个程序,创建三个线程,第一个线程屏幕打印0~9十个数字,第二个线程紧接着打印a~z二十六个小写字母,第三个线程最后再打印A~Z二十六个大写字母。
(1)在linux上编写c语言程序print_threads.c
#include <stdio.h>
#include <pthread.h>
pthread_mutex_t mutex; // 互斥锁
pthread_cond_t cond; // 条件变量
int count = 0; // 计数器
// 打印数字线程函数
void* print_numbers(void* arg) {
pthread_mutex_lock(&mutex); // 上锁
while (count % 3 != 0) {
pthread_cond_wait(&cond, &mutex); // 等待条件变量满足
}
for (int i = 0; i < 10; i++) {
printf("%d ", i);
}
printf("\n");
count++; // 计数器加1
pthread_cond_broadcast(&cond); // 发送信号给其他线程
pthread_mutex_unlock(&mutex); // 解锁
return NULL;
}
// 打印小写字母线程函数
void* print_lowercase(void* arg) {
pthread_mutex_lock(&mutex); // 上锁
while (count % 3 != 1) {
pthread_cond_wait(&cond, &mutex); // 等待条件变量满足
}
for (char c = 'a'; c <= 'z'; c++) {
printf("%c ", c);
}
printf("\n");
count++; // 计数器加1
pthread_cond_broadcast(&cond); // 发送信号给其他线程
pthread_mutex_unlock(&mutex); // 解锁
return NULL;
}
// 打印大写字母线程函数
void* print_uppercase(void* arg) {
pthread_mutex_lock(&mutex); // 上锁
while (count % 3 != 2) {
pthread_cond_wait(&cond, &mutex); // 等待条件变量满足
}
for (char c = 'A'; c <= 'Z'; c++) {
printf("%c ", c);
}
printf("\n");
count++; // 计数器加1
pthread_cond_broadcast(&cond); // 发送信号给其他线程
pthread_mutex_unlock(&mutex); // 解锁
return NULL;
}
int main() {
pthread_t numbers_tid, lowercase_tid, uppercase_tid; // 线程ID
pthread_mutex_init(&mutex, NULL); // 初始化互斥锁
pthread_cond_init(&cond, NULL); // 初始化条件变量
pthread_create(&numbers_tid, NULL, print_numbers, NULL); // 创建打印数字线程
pthread_create(&lowercase_tid, NULL, print_lowercase, NULL); // 创建打印小写字母线程
pthread_create(&uppercase_tid, NULL, print_uppercase, NULL); // 创建打印大写字母线程
pthread_join(numbers_tid, NULL); // 等待打印数字线程结束
pthread_join(lowercase_tid, NULL); // 等待打印小写字母线程结束
pthread_join(uppercase_tid, NULL); // 等待打印大写字母线程结束
pthread_mutex_destroy(&mutex); // 销毁互斥锁
pthread_cond_destroy(&cond); // 销毁条件变量
return 0;
}
(2)用gcc编译器进行程序编译
gcc -pthread -std=c99 -o print_threads print_threads.c
(3)运行程序
./print_threads
2.4. 下面的C程序homework4a.c,采用双线程搜索素数,主函数通过命令行参数接收最大搜索的整数,生成两个线程进行搜索。由于没有采用互斥算法,所以运行时有一些素数会被重复搜索(两个线程都搜索到),请采用互斥量修改代码,使搜索结果不重复。
//homework4a.c
//双线程搜索素数,最简单搜索算法
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h> //atoi()
int m=2; //搜索到哪一个整数
void *search(void *); //定义线程函数
int main(int argc, char **argv)
{
int n;
pthread_t tid1, tid2;
if(argc!=2) {
printf("usage: ./home4a <n>\n");
return 1;
}
n = atoi(argv[1]); //最大搜索整数,从main参数获得
if (n==0){
printf("error: not a integer\n");
return 1;
}
pthread_create(&tid1, NULL, search, (void *)&n);
pthread_create(&tid2, NULL, search, (void *)&n);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}
void *search(void *arg)
{
int N = *(int *)arg;
int flag = 1;
pthread_t mytid = pthread_self();
while(m<=N) {
flag = 1;
for(int j=2; j<=m/2; j++)
if( m%j==0 ) {
flag = 0;
break;
}
if( flag==1)
printf("Thread %lu find: %d\n", mytid, m);
m++;
}
return (void *)NULL;
}
(1)在linux上编写c语言程序homework4a.c
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h> //atoi()
int m = 2; //搜索到哪一个整数
pthread_mutex_t mutex; //定义互斥量
void *search(void *); //定义线程函数
int main(int argc, char **argv)
{
int n;
pthread_t tid1, tid2;
if (argc != 2) {
printf("usage: ./home4a <n>\n");
return 1;
}
n = atoi(argv[1]); //最大搜索整数,从main参数获得
if (n == 0) {
printf("error: not a integer\n");
return 1;
}
pthread_mutex_init(&mutex, NULL); // 初始化互斥量
pthread_create(&tid1, NULL, search, (void *)&n);
pthread_create(&tid2, NULL, search, (void *)&n);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_mutex_destroy(&mutex); // 销毁互斥量
return 0;
}
void *search(void *arg)
{
int N = *(int *)arg;
int flag = 1;
pthread_t mytid = pthread_self();
while (1) { // 使用无限循环
pthread_mutex_lock(&mutex); // 上锁
if (m > N) {
pthread_mutex_unlock(&mutex); // 解锁
break; // 超过最大搜索整数,退出循环
}
int num = m; // 将当前要判断的整数保存到局部变量中
m++;
pthread_mutex_unlock(&mutex); // 解锁
flag = 1;
for (int j = 2; j <= num / 2; j++) {
if (num % j == 0) {
flag = 0;
break;
}
}
if (flag == 1)
printf("Thread %lu find: %d\n", mytid, num);
}
return NULL;
}
(2)用gcc编译器进行程序编译
gcc -pthread -std=c99 -o homework4a homework4a.c
(3)运行程序
./homework4a
2.5. 一堆数字卡片,从1到100。甲乙两人分拣卡片,按顺序分拣,每12张一打,甲负责按顺序挑出卡片,乙负责将12张卡片打包,最后不足12张打一个包。请采用双线程和条件变量、互斥量程序模拟以上过程。
模拟提示:
- 甲乙两个线程;
- 甲线程按顺序打印数字,每到12倍数通知(条件变量)乙线程;
- 乙线程接到通知就打印相应的12倍数的数字;
- 输出样例:
甲挑出:1
甲挑出:2
甲挑出:3
甲挑出:4
甲挑出:5
甲挑出:6
甲挑出:7
甲挑出:8
甲挑出:9
甲挑出:10
甲挑出:11
甲挑出:12
乙打包:1~12
…
(1)在linux上编写c语言程序card.c
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define NUM_CARDS 100
#define PACKAGE_SIZE 12
int cards[NUM_CARDS]; // 数字卡片数组
int card_index = 0; // 当前要挑选的卡片序号
int package_index = 0; // 当前要打包的卡片序号
pthread_mutex_t mutex; // 互斥量
pthread_cond_t cond; // 条件变量
void *thread_jia(void *arg); // 甲线程函数
void *thread_yi(void *arg); // 乙线程函数
void print_package(int start, int end); // 打印打包信息
int main()
{
pthread_t tid1, tid2;
// 初始化互斥量和条件变量
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);
// 初始化数字卡片数组
for (int i = 0; i < NUM_CARDS; i++)
cards[i] = i + 1;
// 创建甲乙两个线程
pthread_create(&tid1, NULL, thread_jia, NULL);
pthread_create(&tid2, NULL, thread_yi, NULL);
// 等待线程结束
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
// 销毁互斥量和条件变量
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
void *thread_jia(void *arg)
{
while (card_index < NUM_CARDS) {
pthread_mutex_lock(&mutex);
if (card_index % PACKAGE_SIZE == 0) {
// 每12张一打,通知乙线程
pthread_cond_signal(&cond);
}
printf("甲挑出:%d\n", cards[card_index]);
card_index++;
pthread_mutex_unlock(&mutex);
}
return NULL;
}
void *thread_yi(void *arg)
{
while (package_index < NUM_CARDS) {
pthread_mutex_lock(&mutex);
while (card_index % PACKAGE_SIZE != 0 && card_index < NUM_CARDS) {
// 等待甲线程的通知
pthread_cond_wait(&cond, &mutex);
}
print_package(package_index, package_index + PACKAGE_SIZE - 1);
package_index += PACKAGE_SIZE;
pthread_mutex_unlock(&mutex);
}
return NULL;
}
void print_package(int start, int end)
{
printf("乙打包:%d~%d\n", cards[start], cards[end < NUM_CARDS ? end : NUM_CARDS - 1]);
}
(2)用gcc编译器进行程序编译
gcc -pthread -std=c99 -o card card.c
(3)运行程序
./card
2.6. 编写一个倒计时软件,主函数接收键盘输入的倒计时任务名(字符串)和倒计时时间(秒),创建线程将倒计时过程打印到屏幕,程序可以多次输入倒计时任务和时间。例如:
输入倒计时任务名:一等奖开奖
输入倒计时时间(s):60
<回车>
一等奖开奖剩余时间60秒
一等奖开奖剩余时间59秒
…
一等奖开奖
…
(1)在linux上编写c语言程序prize.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#define MAX_TASK_LEN 100 // 最大任务名长度
void *timer_thread(void *arg); // 线程函数
int main()
{
char task[MAX_TASK_LEN];
int seconds;
while (1) {
printf("输入倒计时任务名:");
scanf("%s", task);
if (strcmp(task, "exit") == 0) // 输入 exit 退出程序
break;
printf("输入倒计时时间(s):");
scanf("%d", &seconds);
pthread_t tid;
pthread_create(&tid, NULL, timer_thread, (void *)seconds);
// 打印倒计时信息
for (int i = seconds; i >= 0; i--) {
printf("%s剩余时间%d秒\n", task, i);
sleep(1);
}
printf("%s结束\n", task);
pthread_cancel(tid); // 取消线程
pthread_join(tid, NULL); // 等待线程结束
}
return 0;
}
void *timer_thread(void *arg)
{
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); // 设置取消类型为异步
int seconds = (int)arg;
// 打印倒计时信息
for (int i = seconds; i >= 0; i--) {
printf("%d ", i);
fflush(stdout);
sleep(1);
}
printf("\n");
return NULL;
}
(2)用gcc编译器进行程序编译
gcc -pthread -std=c99 -o prize prize.c
(3)运行程序
./prize