操作系统:实验二多线程编程实验

一、实验目的

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 库来创建和管理线程,以及编写多线程程序的方法。

线程的应用特点: 了解线程相对于进程的优点,例如线程的创建和销毁速度快,线程间通信更为简便,资源开销较小等。

用锁机制访问临界区实现互斥的方法: 学习如何使用互斥锁来保护临界区,防止多个线程同时访问共享资源导致的数据不一致或其他问题。

用信号量访问临界区实现互斥的方法: 学习如何使用信号量来实现线程之间的互斥访问,保证在任意时刻只有一个线程能够访问临界资源。

线程下用信号量实现同步操作的方法: 学习如何使用信号量来实现线程之间的同步操作,例如等待某个条件成立或者通知其他线程等。

通过实验目的的学习,可以更深入地了解多线程编程的原理和技术,为开发高效、稳定的多线程应用程序打下基础。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值