【操作系统】多线程同步与互斥

简介: 【操作系统】多线程同步与互斥


一. 实验目的

(1)加强对进程同步和互斥的理解,学会使用信号量解决资源共享问题。

(2)熟悉Linux 进程同步原语。

(3)掌握信号量wait/signal 原语的使用方法,理解信号量的定义、赋初值及wait/signal操作

二. 实验内容

(1)编写程序,使用Linux操作系统中的信号量机制模拟实现生产者-消费者问题。设有一个生产者和一个消费者,缓冲区可以存放产品,生产者不断生成产品放入缓冲区,消费者不断从缓冲区中取出产品,消费产品。

(2)以上实验只模拟了一个产品的放入与取出,请修改代码,以模拟实现多个产品的放入与取出。

三. 实验步骤

(1)编写程序,使用Linux操作系统中的信号量机制模拟实现生产者-消费者问题。设有一个生产者和一个消费者,缓冲区可以存放产品,生产者不断生成产品放入缓冲区,消费者不断从缓冲区中取出产品,消费产品。

#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#include <semaphore.h>
#define SIZE 256
sem_t empty, full, mutex;
char *buffer;
void *producer()
{
    sem_wait(&empty); 
    sem_wait(&mutex); 
    printf("input something to buffer:\n");
    buffer = (char *)malloc(SIZE);
    fgets(buffer, SIZE, stdin);
    sem_post(&mutex); 
    sem_post(&full); 
}
void *consumer()
{
    sem_wait(&full); 
    sem_wait(&mutex);
    printf("read product from buffer: %s\n", buffer);
    memset(buffer, 0, SIZE);
    sem_post(&mutex); 
    sem_post(&empty); 
}
int main()
{
    pthread_t id_producer, id_consumer;
    int ret;
    sem_init(&empty, 0, 1); 
    sem_init(&full, 0, 0); 
    sem_init(&mutex, 0, 1); 
    ret = pthread_create(&id_producer, NULL, producer, NULL);
    ret = pthread_create(&id_consumer, NULL, consumer, NULL); 
    pthread_join(id_producer, NULL);
    pthread_join(id_consumer, NULL);
    sem_destroy(&empty);
    sem_destroy(&full);
    sem_destroy(&mutex);
    printf("The End ...\n");
    return 0;
}

该程序实现了生产者和消费者问题的解决方案,使用了三个信号量(empty、full和mutex)来控制生产和消费的进程访问缓冲区。

首先,程序定义了一个缓冲区(buffer),大小为256字节,用于生产者和消费者的数据交换。然后,使用了三个信号量来控制进程访问缓冲区的情况,其中,empty是缓冲区为空时的信号量,full是缓冲区为满时的信号量,mutex是用于保护缓冲区的互斥锁。

在producer函数中,生产者使用了P操作(sem_wait)操作empty和mutex信号量。如果empty信号量的值大于0,则表示缓冲区未满,可以向缓冲区中添加数据,否则就需要等待。同时,使用互斥锁mutex来保证缓冲区的同步访问。之后,生产者从标准输入中读取数据,将数据添加到缓冲区,并使用V操作(sem_post)操作mutex和full信号量,以此通知消费者缓冲区已经有数据可以读取。

在consumer函数中,消费者使用了P操作操作full和mutex信号量。如果full信号量的值大于0,则表示缓冲区有数据,可以读取,否则就需要等待。同样地,使用互斥锁mutex来保证缓冲区的同步访问。之后,消费者从缓冲区中读取数据,并将缓冲区清空。最后,使用V操作操作mutex和empty信号量,以此通知生产者缓冲区已经可写入新的数据。

在main函数中,创建了生产者和消费者进程,并等待它们完成。最后,使用sem_destroy函数来销毁信号量,并输出程序结束的信息。

总之,该程序使用了信号量来实现线程之间的同步,解决了生产者和消费者问题,保证了线程安全性和数据交换的可靠性。


(2)以上实验只模拟了一个产品的放入与取出,请修改代码,以模拟实现多个产品的放入与取出。

#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#include <semaphore.h>
#define SIZE 256
sem_t empty, full, mutex;
char *buffer;
void *producer()
{
    while(1){
  sem_wait(&empty); 
  sem_wait(&mutex); 
  printf("input something to buffer:\n");
  buffer = (char *)malloc(SIZE);
  fgets(buffer, SIZE, stdin);
  sem_post(&mutex); 
  sem_post(&full); 
    }
}
void *consumer()
{
    while(1){
  sem_wait(&full); 
  sem_wait(&mutex);
  printf("read product from buffer: %s\n", buffer);
  memset(buffer, 0, SIZE);
  sem_post(&mutex); 
  sem_post(&empty); 
    }
}
int main()
{
    pthread_t id_producer, id_consumer;
    int ret;
    sem_init(&empty, 0, 1); 
    sem_init(&full, 0, 0); 
    sem_init(&mutex, 0, 1); 
    ret = pthread_create(&id_producer, NULL, producer, NULL);
    ret = pthread_create(&id_consumer, NULL, consumer, NULL); 
    pthread_join(id_producer, NULL);
    pthread_join(id_consumer, NULL);
    sem_destroy(&empty);
    sem_destroy(&full);
    sem_destroy(&mutex);
    printf("The End ...\n");
    return 0;
}

该程序是一个典型的生产者消费者问题的解决方案,使用了三个信号量(empty、full和mutex)来控制生产者和消费者访问缓冲区。不同之处在于,程序使用了while(1)循环,可以使生产者和消费者持续地向缓冲区中添加或读取数据,并避免在处理完一次数据后就退出的情况发生。

程序开始时,定义了一个缓冲区(buffer),大小为256字节,用于生产者和消费者的数据交换。接着,使用了三个信号量来控制进程访问缓冲区的情况,其中,empty是缓冲区为空时的信号量,full是缓冲区为满时的信号量,mutex是用于保护缓冲区的互斥锁。

在producer函数中,生产者使用了while(1)循环,表示生产者会一直运行下去。在循环中,生产者使用了P操作(sem_wait)操作empty和mutex信号量。如果empty信号量的值大于0,则表示缓冲区未满,可以向缓冲区中添加数据,否则就需要等待。同时,使用互斥锁mutex来保证缓冲区的同步访问。之后,生产者从标准输入中读取数据,将数据添加到缓冲区,并使用V操作(sem_post)操作mutex和full信号量,以此通知消费者缓冲区已经有数据可以读取。

在consumer函数中,消费者使用了while(1)循环,表示消费者会一直运行下去。在循环中,消费者使用了P操作操作full和mutex信号量。如果full信号量的值大于0,则表示缓冲区有数据,可以读取,否则就需要等待。同样地,使用互斥锁mutex来保证缓冲区的同步访问。之后,消费者从缓冲区中读取数据,并将缓冲区清空。最后,使用V操作操作mutex和empty信号量,以此通知生产者缓冲区已经可写入新的数据。

在main函数中,创建了生产者和消费者线程,并等待它们完成。最后,使用sem_destroy函数来销毁信号量,并输出程序结束的信息。

总之,该程序使用了信号量和互斥锁来实现线程之间的同步,解决了生产者和消费者问题,保证了线程安全性和数据交换的可靠性。使用while(1)循环可以使生产者和消费者持续地向缓冲区中添加或读取数据,并提高了程序的实用性。

四. 实验结果

(1)编写程序,使用Linux操作系统中的信号量机制模拟实现生产者-消费者问题。设有一个生产者和一个消费者,缓冲区可以存放产品,生产者不断生成产品放入缓冲区,消费者不断从缓冲区中取出产品,消费产品。

(2)以上实验只模拟了一个产品的放入与取出,请修改代码,以模拟实现多个产品的放入与取出。

五. 实验总结

本实验是通过对Linux操作系统中的信号量机制进行模拟实现,来深入理解多线程同步与互斥的概念和基本方法,同时也是对生产者-消费者模型的一个实际应用。

通过本次实验,我们深入了解了信号量的概念、使用方法以及相关的函数,并通过对生产者-消费者问题的模拟,掌握了相关的同步互斥机制。在实现过程中,我们使用了一个互斥信号量mutex来控制缓冲区和生产者、消费者之间的竞争,并使用了两个信号量full,empty来分别控制缓冲区是否已满或已空,从而实现了生产者和消费者之间的同步与互斥。

在实验中,我们还发现了一些问题和需要注意的地方。例如,在使用信号量时,需要保证每次操作都是原子操作,否则可能出现竞争条件,从而破坏了同步互斥机制。此外,还需要注意信号量的初始化以及销毁等问题,否则可能会出现资源泄漏等问题。

通过本次实验,我们不仅实现了生产者-消费者模型,并且深入理解了多线程同步与互斥的概念和方法,同时也锻炼了自己的编程能力和阅读和理解他人代码的能力。在今后的学习和工作中,我们将更好地应用信号量机制和多线程编程,并不断深化对操作系统和计算机原理的理解。

目录
相关文章
|
18天前
|
调度 开发者 Python
深入浅出操作系统:进程与线程的奥秘
在数字世界的底层,操作系统扮演着不可或缺的角色。它如同一位高效的管家,协调和控制着计算机硬件与软件资源。本文将拨开迷雾,深入探索操作系统中两个核心概念——进程与线程。我们将从它们的诞生谈起,逐步剖析它们的本质、区别以及如何影响我们日常使用的应用程序性能。通过简单的比喻,我们将理解这些看似抽象的概念,并学会如何在编程实践中高效利用进程与线程。准备好跟随我一起,揭开操作系统的神秘面纱,让我们的代码运行得更加流畅吧!
|
16天前
|
算法 调度 开发者
深入理解操作系统:进程与线程的管理
在数字世界的复杂编织中,操作系统如同一位精明的指挥家,协调着每一个音符的奏响。本篇文章将带领读者穿越操作系统的幕后,探索进程与线程管理的奥秘。从进程的诞生到线程的舞蹈,我们将一起见证这场微观世界的华丽变奏。通过深入浅出的解释和生动的比喻,本文旨在揭示操作系统如何高效地处理多任务,确保系统的稳定性和效率。让我们一起跟随代码的步伐,走进操作系统的内心世界。
|
23天前
|
供应链 安全 NoSQL
PHP 互斥锁:如何确保代码的线程安全?
在多线程和高并发环境中,确保代码段互斥执行至关重要。本文介绍了 PHP 互斥锁库 `wise-locksmith`,它提供多种锁机制(如文件锁、分布式锁等),有效解决线程安全问题,特别适用于电商平台库存管理等场景。通过 Composer 安装后,开发者可以利用该库确保在高并发下数据的一致性和安全性。
35 6
|
1月前
|
Linux 调度 C语言
深入理解操作系统:进程和线程的管理
【10月更文挑战第32天】本文旨在通过浅显易懂的语言和实际代码示例,带领读者探索操作系统中进程与线程的奥秘。我们将从基础知识出发,逐步深入到它们在操作系统中的实现和管理机制,最终通过实践加深对这一核心概念的理解。无论你是编程新手还是希望复习相关知识的资深开发者,这篇文章都将为你提供有价值的见解。
|
1月前
深入理解操作系统:进程与线程的管理
【10月更文挑战第30天】操作系统是计算机系统的核心,它负责管理计算机硬件资源,为应用程序提供基础服务。本文将深入探讨操作系统中进程和线程的概念、区别以及它们在资源管理中的作用。通过本文的学习,读者将能够更好地理解操作系统的工作原理,并掌握进程和线程的管理技巧。
44 2
|
1月前
|
调度 Python
深入浅出操作系统:进程与线程的奥秘
【10月更文挑战第28天】在数字世界的幕后,操作系统悄无声息地扮演着关键角色。本文将拨开迷雾,深入探讨操作系统中的两个基本概念——进程和线程。我们将通过生动的比喻和直观的解释,揭示它们之间的差异与联系,并展示如何在实际应用中灵活运用这些知识。准备好了吗?让我们开始这段揭秘之旅!
|
1月前
|
Java 调度
Java 线程同步的四种方式,最全详解,建议收藏!
本文详细解析了Java线程同步的四种方式:synchronized关键字、ReentrantLock、原子变量和ThreadLocal,通过实例代码和对比分析,帮助你深入理解线程同步机制。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
Java 线程同步的四种方式,最全详解,建议收藏!
|
2月前
|
安全 Java 开发者
Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用
本文深入解析了Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用。通过示例代码展示了如何正确使用这些方法,并分享了最佳实践,帮助开发者避免常见陷阱,提高多线程程序的稳定性和效率。
48 1
|
1月前
|
Linux 调度
探索操作系统核心:进程与线程管理
【10月更文挑战第24天】在数字世界的心脏,操作系统扮演着至关重要的角色。它不仅是计算机硬件与软件之间的桥梁,更是管理和调度资源的大管家。本文将深入探讨操作系统的两大基石——进程与线程,揭示它们如何协同工作以确保系统运行得井井有条。通过深入浅出的解释和直观的代码示例,我们将一起解锁操作系统的管理奥秘,理解其对计算任务高效执行的影响。
|
2月前
|
安全 调度 C#
STA模型、同步上下文和多线程、异步调度
【10月更文挑战第19天】本文介绍了 STA 模型、同步上下文和多线程、异步调度的概念及其优缺点。STA 模型适用于单线程环境,确保资源访问的顺序性;同步上下文和多线程提高了程序的并发性和响应性,但增加了复杂性;异步调度提升了程序的响应性和资源利用率,但也带来了编程复杂性和错误处理的挑战。选择合适的模型需根据具体应用场景和需求进行权衡。