一、实验目的
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;
}
编译及执行过程和运行结果截屏:
结果分析:
两个线程的执行是并发的,它们各自在不同的时间段内执行打印操作,因此会交错输出。
(2)
#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;
}
编译及执行过程和运行结果截屏:
结果分析:
主线程在创建完两个线程后,调用 pthread_join 来等待线程结束并回收资源,保证主线程在子线程执行完毕后再结束。最后调用 pthread_mutex_destroy 来销毁互斥锁。因为互斥锁确保了在任意时刻只有一个线程可以执行打印操作,因此输出是顺序的。
(3)
#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;
}
编译及执行过程和运行结果截屏:
结果分析:
在 printer 函数中,使用 sem_wait 来等待信号量 semA 的值,如果值为大于等于1,则减1,表示申请资源成功,否则阻塞等待;使用 sem_post 来释放信号量 semA,将其值加1,表示释放资源。
因为 semA 是二元信号量,所以只有一个线程能够通过 sem_wait 成功,另一个线程会阻塞等待。
主线程在创建完两个线程后,调用 pthread_join 来等待线程结束并回收资源,保证主线程在子线程执行完毕后再结束。
最后调用 sem_destroy 来销毁信号量。
使用了信号量 semA
来确保了线程之间的同步,保证了字符串的顺序输出。
2、通过多线程模拟多窗口售票,在主线程下创建4个子线程,模拟4个售票窗口,假设有20张票待售,运行该程序看会有什么样的结果,分析程序和执行结果。(1分)
<参考程序>
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.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=%d\n",(int)ans);
}
return 0;
}
给出编译及执行过程和运行结果:(部分截屏)
结果分析:
个线程同时在执行售票操作,并且没有适当的同步机制来控制对ticket_sum变量的访问。这导致了竞态条件,多个线程可能同时读取相同的ticket_sum值,并且在进行减法操作之前没有进行适当的检查。
票已经开始被售出,但是售票的顺序混乱,这是因为多个线程并发地尝试进行售票操作,而没有对输出进行适当的同步
在售票时没有正确检查ticket_sum的值是否大于0,导致了ticket_sum变为负数。
3、修改上题,用锁机制实现线程互斥进入临界区,解决售票窗口超卖问题。要求给出编译及运行过程和结果截图。 (2分)
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int ticket_sum = 20;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void *sell_ticket(void *arg) {
while (1) {
pthread_mutex_lock(&mutex);
if (ticket_sum > 0) {
sleep(1);
printf("sell the %dth\n", 20 - ticket_sum + 1);
ticket_sum--;
} else {
pthread_mutex_unlock(&mutex);
break;
}
pthread_mutex_unlock(&mutex);
}
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\n", 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 error, flag=%d\n", tids[i], flag);
return flag;
}
printf("ans=%d\n", (int)ans);
}
pthread_mutex_destroy(&mutex);
return 0;
}
4、修改实验内容2,用信号量实现线程互斥进入临界区,解决售票窗口超卖问题。要求给出编译及执行过程和结果截屏。(2分)
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <semaphore.h>
int ticket_sum = 20;
sem_t mutex;
void *sell_ticket(void *arg) {
while (1) {
sem_wait(&mutex);
if (ticket_sum > 0) {
sleep(1);
printf("sell the %dth\n", 20 - ticket_sum + 1);
ticket_sum--;
} else {
sem_post(&mutex);
break;
}
sem_post(&mutex);
}
return 0;
}
int main() {
int flag, i;
pthread_t tids[4];
sem_init(&mutex, 0, 1);
for (i = 0; i < 4; i++) {
flag = pthread_create(&tids[i], NULL, &sell_ticket, NULL);
if (flag) {
printf("pthread create error, flag=%d\n", 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 error, flag=%d\n", tids[i], flag);
return flag;
}
printf("ans=%d\n", (int)ans);
}
sem_destroy(&mutex);
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 <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#define BUFFER_SIZE 5
#define PRODUCER_NUM 2
#define CONSUMER_NUM 2
int buffer[BUFFER_SIZE];
sem_t empty, full, mutex;
int in = 0, out = 0;
void *producer(void *arg) {
int id = *(int *)arg;
int item = 1;
while (item <= 15) {
sem_wait(&empty); // 检查缓冲区是否有空位
sem_wait(&mutex); // 互斥访问缓冲区
buffer[in] = item; // 将产品放入缓冲区
printf("生产者 %d 生产产品 %d\n", id, item);
in = (in + 1) % BUFFER_SIZE;
sem_post(&mutex);
sem_post(&full); // 通知消费者有产品可消费
item++;
sleep(1);
}
return NULL;
}
void *consumer(void *arg) {
int id = *(int *)arg;
while (1) {
sem_wait(&full); // 检查缓冲区是否有产品可消费
sem_wait(&mutex); // 互斥访问缓冲区
int item = buffer[out]; // 从缓冲区取出产品
printf("消费者 %d 消费产品 %d\n", id, item);
out = (out + 1) % BUFFER_SIZE;
sem_post(&mutex);
sem_post(&empty); // 通知生产者有空位可生产
if (item == 15) // 如果产品为15,则退出循环
break;
sleep(2);
}
return NULL;
}
int main() {
pthread_t producer_threads[PRODUCER_NUM], consumer_threads[CONSUMER_NUM];
int producer_ids[PRODUCER_NUM], consumer_ids[CONSUMER_NUM];
sem_init(&empty, 0, BUFFER_SIZE);
sem_init(&full, 0, 0);
sem_init(&mutex, 0, 1);
// 创建生产者线程
for (int i = 0; i < PRODUCER_NUM; ++i) {
producer_ids[i] = i + 1;
pthread_create(&producer_threads[i], NULL, producer, &producer_ids[i]);
}
// 创建消费者线程
for (int i = 0; i < CONSUMER_NUM; ++i) {
consumer_ids[i] = i + 1;
pthread_create(&consumer_threads[i], NULL, consumer, &consumer_ids[i]);
}
// 等待所有线程结束
for (int i = 0; i < PRODUCER_NUM; ++i) {
pthread_join(producer_threads[i], NULL);
}
for (int i = 0; i < CONSUMER_NUM; ++i) {
pthread_join(consumer_threads[i], NULL);
}
// 销毁信号量
sem_destroy(&empty);
sem_destroy(&full);
sem_destroy(&mutex);
return 0;
}
三、实验总结和体会(1分)
通过学习本实验学到了以下知识:
线程的概念和进程的区别: 了解线程是程序执行流的最小单元,与进程的区别在于线程共享同一地址空间和其他资源,而进程拥有独立的地址空间。
Linux 下线程的创建方法及编程: 学习如何在 Linux 环境下使用 pthread 库来创建和管理线程,以及编写多线程程序的方法。
线程的应用特点: 了解线程相对于进程的优点,例如线程的创建和销毁速度快,线程间通信更为简便,资源开销较小等。
用锁机制访问临界区实现互斥的方法: 学习如何使用互斥锁来保护临界区,防止多个线程同时访问共享资源导致的数据不一致或其他问题。
用信号量访问临界区实现互斥的方法: 学习如何使用信号量来实现线程之间的互斥访问,保证在任意时刻只有一个线程能够访问临界资源。
线程下用信号量实现同步操作的方法: 学习如何使用信号量来实现线程之间的同步操作,例如等待某个条件成立或者通知其他线程等。
通过实验目的的学习,可以更深入地了解多线程编程的原理和技术,为开发高效、稳定的多线程应用程序打下基础。