实验二 多线程编程实验
一、实验目的
1、掌握线程的概念,明确线程和进程的区别。
2、学习Linux下线程创建方法及编程。
3、了解线程的应用特点。
4、掌握用锁机制访问临界区实现互斥的方法。
5、掌握用信号量访问临界区实现互斥的方法。
6、掌握线程下用信号量实现同步操作的方法。
二、实验内容
1、运行下列程序,给出执行结果,并分析运行结果。(3分)
1) #include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// 打印函数(在屏幕上显示字符串)
void printer(char *str){
while(*str!='\0')
{ putchar(*str);
fflush(stdout);
str++;
sleep(1);
}
printf("\n");
}
// 线程一
void *thread_fun_1(void *arg)
{
char *str = "hello";
printer(str); //调用打印函数
}
// 线程二
void *thread_fun_2(void *arg)
{
char *str = "world";
printer(str); //调用打印函数
}
int main(void)
{
pthread_t tid1, tid2;
// 创建 2 个线程
pthread_create(&tid1, NULL, thread_fun_1, NULL);
pthread_create(&tid2, NULL, thread_fun_2, NULL);
// 等待线程结束,回收其资源
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}
编译及执行过程和运行结果截屏:
结果分析:
线程一和线程二,它们分别打印"hello"和"world"字符串。主线程使用pthread_join函数等待两个线程结束。
由于并发执行的原因,线程一和线程二的打印操作会交替进行,输出的结果可能是"howorldello"、"howerlllod"等不确定的顺序。
每个字符之间有1秒的延迟,所以输出的结果中每个字符之间会有间隔。最后输出一个换行符,每次执行的结果都会在新的一行显示。
- #include <stdio.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t mutex_x=PTHREAD_MUTEX_INITIALIZER;//定义并初始化锁
//打印函数(在屏幕上显示字符串)
void printer(char *str)
{
pthread_mutex_lock(&mutex_x);//上锁
while(*str!='\0')
{
putchar(*str);
fflush(stdout);
str++;
sleep(1);
}
printf("\n");
pthread_mutex_unlock(&mutex_x);//解锁
}
// 线程一
void *thread_fun_1(void *arg)
{
char *str = "hello";
printer(str); //调用打印函数
}
// 线程二
void *thread_fun_2(void *arg)
{
char *str = "world";
printer(str); //调用打印函数
}
int main(void)
{
pthread_t tid1, tid2;
// 创建 2 个线程
pthread_create(&tid1, NULL, thread_fun_1, NULL);
pthread_create(&tid2, NULL, thread_fun_2, NULL);
// 等待线程结束,回收其资源
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_mutex_destroy(&mutex_x); //销毁互斥锁
return 0;
}
编译及执行过程和运行结果截屏:
结果分析:
在程序中创建了两个线程,分别执行thread_fun_1和thread_fun_2函数。这两个函数都调用了printer函数,每次调用时都会上锁mutex_x,然后通过putchar函数逐个打印字符串中的字符,并休眠1秒钟。
由于两个线程同时运行,打印函数的执行是交替进行的。线程一先执行,打印"hello",然后释放锁,线程二获取到锁,打印"world",最后两个线程结束。
所以最终的输出结果为"hello world"。
- #include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>
sem_t semA; //声明一个名为semA的信号量变量
//打印函数(在屏幕上显示字符串)
void printer(char *str)
{
sem_wait(&semA);//申请信号量(P操作)
while(*str!='\0')
{
putchar(*str);
fflush(stdout);
str++;
sleep(1);
}
printf("\n");
sem_post(&semA);//释放信号量(V操作)
}
// 线程一
void *thread_fun_1(void *arg)
{
char *str = "hello";
printer(str); //调用打印函数}
// 线程二
void *thread_fun_2(void *arg)
{
char *str = "world";
printer(str); //调用打印函数}
int main(void)
{
pthread_t tid1, tid2;
if(sem_init(&semA, 0, 1)) //初始化信号量的值为1(二元信号量)
printf("error sem_init!\n");
// 创建 2 个线程
pthread_create(&tid1, NULL, thread_fun_1, NULL);
pthread_create(&tid2, NULL, thread_fun_2, NULL);
// 等待线程结束,回收其资源
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
sem_destroy(&semA); //销毁信号量
return 0;
}
编译及执行过程和运行结果截屏:
结果分析:
在主函数中,创建了两个线程,分别调用了thread_fun_1和thread_fun_2函数。这两个函数内部调用了printer函数。由于两个线程同时调用printer函数,但是通过信号量的控制,确保了只有一个线程可以打印字符,另一个线程在sem_wait(&semA)处等待。因此,打印的结果是"helloworld"。
2、通过多线程模拟多窗口售票,在主线程下创建4个子线程,模拟4个售票窗口,假设有20张票待售,运行该程序看会有什么样的结果,分析程序和执行结果。(1分)
<参考程序>
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<semaphore.h>
#include<stdint.h>
int ticket_sum=20;
void *sell_ticket(void *arg)
{
int i;
for(i=0;i<20;i++)
{
if(ticket_sum>0)
{
sleep(1);
printf("sell the %dth\n",20-ticket_sum+1);
ticket_sum--;
}
}
return 0;
}
int main()
{
int flag,i;
pthread_t tids[4];
for(i=0;i<4;i++)
{
flag=pthread_create(&tids[i],NULL,&sell_ticket,NULL);//创建线程
if(flag)
{
printf("pthread create error ,flag=%d",flag);
return flag;
}
}
sleep(20);
void *ans;
for(i=0;i<4;i++)
{
flag=pthread_join(tids[i],&ans);//等待线程结束
if(flag)
{
printf("tid=%lu,join erro flag=%d",tids[i],flag);
return flag;
}
printf("ans=%ld\n",(intptr_t)ans);
}
return 0;
}给出编译及执行过程和运行结果:(部分截屏)
结果分析:
该程序创建了4个线程,每个线程都执行sell_ticket函数。在sell_ticket函数中,使用一个循环进行售票操作,每次售票后ticket_sum减1,直到ticket_sum为0结束循环。
在主函数中,创建4个线程,并等待线程完成。等待线程完成后,打印线程的返回值,此处返回值为0。
3、修改上题,用锁机制实现线程互斥进入临界区,解决售票窗口超卖问题。要求给出编译及运行过程和结果截图。 (2分)
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include<semaphore.h>
#include<stdint.h>
int ticket_sum = 20; // 总票数
pthread_mutex_t mutex; // 定义互斥锁
void *sell_ticket(void *arg)
{
int ticket_count = 0; // 售出的票数
while (1) {
pthread_mutex_lock(&mutex); // 上锁
if (ticket_sum > 0) {
sleep(1); // 模拟售票耗时
printf("sell the %d th\n", 20 - ticket_sum + 1);
ticket_sum--;
ticket_count++;
} else {
pthread_mutex_unlock(&mutex); // 解锁
break;
}
pthread_mutex_unlock(&mutex); // 解锁
}
pthread_exit((void *)ticket_count); // 返回售出的票数
}
int main()
{
pthread_t tids[4]; // 线程数组
int i;
int ticket_count = 0; // 总共售出的票数
pthread_mutex_init(&mutex, NULL); // 初始化互斥锁
for (i = 0; i < 4; i++) {
pthread_create(&tids[i], NULL, sell_ticket, NULL); // 创建线程
}
for (i = 0; i < 4; i++) {
void *ret;
pthread_join(tids[i], &ret); // 等待线程结束
ticket_count += (intptr_t)ret; // 统计每个线程售出的票数
}
printf("总共售出了 %d 张票\n", ticket_count);
pthread_mutex_destroy(&mutex); // 销毁互斥锁
return 0;
}
给出编译及执行过程和运行结果:(部分截屏)
4、修改实验内容2,用信号量实现线程互斥进入临界区,解决售票窗口超卖问题。要求给出编译及执行过程和结果截屏。(2分)
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include<semaphore.h>
#include<stdint.h>
#define TOTAL_TICKETS 20
int tickets = TOTAL_TICKETS;
sem_t semaphore;
void *sellTicket(void *arg) {
char *sellerName = (char *)arg;
while (1) {
sem_wait(&semaphore); // 获得信号量,进入临界区
if (tickets > 0) {
tickets--;
printf("sell the %dth\n", 20-tickets);
} else {
printf("没有票了\n");
sem_post(&semaphore); // 离开临界区
break;
}
sem_post(&semaphore); // 离开临界区
usleep(100000); // 模拟售票过程的耗时操作
}
return NULL;
}
int main() {
pthread_t sellers[4];
sem_init(&semaphore, 0, 1);
char *sellerNames[4] = {"售票窗口1", "售票窗口2", "售票窗口3", "售票窗口4"};
for (int i=0; i<4; i++) {
pthread_create(&sellers[i], NULL, sellTicket, (void *)sellerNames[i]);
}
for (int i=0; i<4; i++) {
pthread_join(sellers[i], NULL);
}
sem_destroy(&semaphore);
return 0;
}
给出编译及执行过程和运行结果:(部分截屏)
5、利用线程和信号量机制实现司机售票员同步操作问题。(1分)
参考程序框架:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
sem_t door, stop; // 设置关门和停车两个信号量
void *thread_driver(void *arg) // 司机线程
{
while (1)
{
sem_wait(&door); // P(door),等待售票员的关门信号
printf("司机: 启动汽车\n");
printf("司机: 驾驶汽车\n");
sleep(1);
printf("司机: 到站停车\n");
sem_post(&stop); // V(stop),发送停车信号
}
}
void *thread_conductor(void *arg) // 售票员线程
{
while (1)
{
printf("售票员: 关门\n");
sem_post(&door); // V(door),发送关门信号
printf("售票员: 卖票\n");
sem_wait(&stop); // P(stop),等待司机的停车信号
printf("售票员: 开门\n");
printf("乘客上下车\n");
sleep(1);
}
}
int main()
{
int sg1, sg2;
pthread_t driver, conductor; // 定义两个变量存放线程标识符
sg1 = sem_init(&door, 0, 0); // 初始化关门信号量door,初始值为0
sg2 = sem_init(&stop, 0, 0); // 初始化停车信号量stop,初始值为0
pthread_create(&driver, NULL, (void *)thread_driver, NULL); // 创建司机线程
pthread_create(&conductor, NULL, (void *)thread_conductor, NULL); // 创建售票员线程
pthread_join(driver, NULL); // 等待司机线程结束
pthread_join(conductor, NULL); // 等待售票员线程结束
sem_destroy(&door); // 销毁关门信号量
sem_destroy(&stop); // 销毁停车信号量
return 0;
}
编译及执行过程和结果截屏:
6、利用线程和信号量实现生产者消费者问题(涉及线程同步和互斥问题)。(附加题)
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
#define BUFFER_SIZE 5
int buffer[BUFFER_SIZE];
sem_t mutex, empty, full;
int in = 0;
int out = 0;
void *producer(void *arg) {
int item;
while (1) {
item = rand() % 100; // 生产一个随机数作为产品
sem_wait(&empty); // 等待空缓冲区
sem_wait(&mutex); // 互斥访问缓冲区
printf("Produced item: %d\n", item);
buffer[in] = item;
in = (in + 1) % BUFFER_SIZE;
sem_post(&mutex); // 释放互斥锁
sem_post(&full); // 增加满缓冲区的信号量
sleep(1); // 生产速度较慢,方便观察
}
}
void *consumer(void *arg) {
int item;
while (1) {
sem_wait(&full); // 等待满缓冲区
sem_wait(&mutex); // 互斥访问缓冲区
item = buffer[out];
out = (out + 1) % BUFFER_SIZE;
printf("Consumed item: %d\n", item);
sem_post(&mutex); // 释放互斥锁
sem_post(&empty); // 增加空缓冲区的信号量
sleep(1); // 消费速度较慢,方便观察
}
}
int main() {
pthread_t producer_thread, consumer_thread;
sem_init(&mutex, 0, 1); // 初始化互斥锁
sem_init(&empty, 0, BUFFER_SIZE); // 初始化空缓冲区的信号量
sem_init(&full, 0, 0); // 初始化满缓冲区的信号量
pthread_create(&producer_thread, NULL, producer, NULL);
pthread_create(&consumer_thread, NULL, consumer, NULL);
pthread_join(producer_thread, NULL);
pthread_join(consumer_thread, NULL);
sem_destroy(&mutex); // 销毁互斥锁
sem_destroy(&empty); // 销毁空缓冲区的信号量
sem_destroy(&full); // 销毁满缓冲区的信号量
return 0;
}
编译及执行过程和结果截屏:
三、实验总结和体会
通过这次实验,我对操作系统中多线程编程有了更深入的了解和实践经验。在实验中,我学习了使用多线程编程来实现并发任务的处理,以及线程间的同步和互斥机制。
首先,我学会了使用线程创建函数来创建和启动新线程。我了解到线程是程序中的并发执行流,可以并行处理任务,提高程序的执行效率。在实验中,我通过使用pthread库中的函数,如pthread_create()来创建线程,并指定线程函数进行任务处理。
其次,我学会了使用互斥锁来保护共享资源的访问。在多线程编程中,当多个线程需要同时访问共享资源时,可能会导致竞态条件和数据不一致的问题。为了避免这些问题,我使用了互斥锁来实现线程间的互斥访问。通过使用pthread库中的函数,如pthread_mutex_init()和pthread_mutex_lock()等,我可以对关键代码段进行上锁,使得同一时间只有一个线程可以访问共享资源。
此外,我还学习了使用条件变量来实现线程间的通信和同步。条件变量可以用于线程间的等待和唤醒操作,使得线程可以在特定条件满足时才进行执行。通过使用pthread库中的函数,如pthread_cond_init()和pthread_cond_wait()等,我可以实现线程的等待和唤醒操作,以实现线程间的同步和协作。
通过这次实验,我对操作系统中多线程编程的原理和实践有了更深入的理解。我认识到多线程编程可以提高程序的并发性和效率,但也需要考虑线程间的同步和互斥,以及资源管理和性能优化等方面。我意识到多线程编程的复杂性和挑战,但也体会到了并发编程的用处