【问题标题】:Monitor assignment: double buffer (condition variables and mutex)监视器分配:双缓冲区(条件变量和互斥锁)
【发布时间】:2022-01-25 13:58:45
【问题描述】:

大家好,我正在尝试为大学做作业,但在使用条件变量和多线程时遇到了一些问题。每次我运行程序都会出现死锁。在这里我留下了我的(错误)解决方案的练习文本,我希望有人可以帮助我,谢谢。

TEXT___ 监视器分配:双缓冲 三个并发线程 P1、P2 和 C 使用共享缓冲区进行协作。线程 P1 使用一种称为 push_int 的方法连续生成一个整数并将其传输到缓冲区。线程 P2 连续产生一对整数,并使用一种称为 push_pair 的方法将它们传送到缓冲区。线程 C 使用一种称为 fetch 的方法不断地从缓冲区中获取一个值。缓冲区有两个槽,最初是空的。

这些方法应该如下工作: push_int 在缓冲区的任何空槽中写入一个数字。如果缓冲区已满,则 push_int 阻塞调用线程,直到有一个空槽;但是,如果另一个线程正在等待使用 push_pair 将一对数字写入缓冲区,则 push_int 会阻塞调用线程,直到不仅有一个空槽,而且 push_pair 上没有其他线程阻塞; push_pair 在缓冲区中写入一对数字。如果一个或多个缓冲区槽已满,则 push_pair 阻塞调用线程,直到缓冲区完全为空; fetch 返回缓冲区中存在的两个数字之一(无论哪个都没有关系),并将相应的插槽标记为空;如果缓冲区完全为空,则 fetch 返回 0,而不会阻塞调用线程。 分析问题。设计、实现和测试一个提供 push_int(int)、push_pair(int, int) 和 fetch() 方法的监视器。一定要避免死锁以及任何竞争条件(换句话说,缓冲区必须正常工作)。此外,应该保留并发性:线程应该尽可能并发进行,而不是不必要地阻塞。

将您的工作源代码作为一个或多个 .c/.h 文件提交,并将您的项目作为单个 .pdf 或 .txt 文档提交。或者,您可以将所有内容作为单个 .zip 包一起提交。不要提交可执行文件。项目文件应包含您对监视器的设计,您可以在其中简要解释实施背后的总体思路。如果您愿意,可以添加您认为相关和值得注意的任何内容。

只要设计是您自己的,您就可以从常用模板开始并重用您希望的任何代码部分。 __我的解决方案

/*
 ============================================================================
 Name        : monitor-template.c
 Author      : 
 Version     : Dec 24, 2021
 Copyright   : Use as you wish
 Description : Template for the implementation of a monitor and its animation
 ============================================================================
 */

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/types.h>

// CONSTANTS AND MACROS

#define N_THREADS 3
#define N_SLOTS 2
#define Empty 1     //True  = Empty slot
#define Full 0      //False = FUll slot

#define FOREVER for(;;)

// DEFINITIONS OF NEW DATA TYPES

typedef char thread_name_t[10];

typedef struct {
    pthread_mutex_t m;
    pthread_cond_t cv;
    int BUFFER[N_SLOTS];
    int n_w;
    int slots[N_SLOTS];
} monitor_t;

// GLOBAL VARIABLES

monitor_t mon;

//  MONITOR API
void push_int(monitor_t *mon, int item);
void push_pair(monitor_t *mon, int item1, int item2);
int fetch(monitor_t *mon);
void monitor_init(monitor_t *mon);
void monitor_destroy(monitor_t *mon);

// OTHER FUNCTION DECLARATIONS

void *write_int();
void *write_pair();
void *read_buffer();

double spend_some_time(int);

// IMPLEMENTATION OF MONITOR API

void monitor_init(monitor_t *mon) {
    pthread_mutex_init(&mon -> m, NULL);
    pthread_cond_init(&mon -> cv, NULL);
    mon -> n_w = 0;
    for (int i = 0; i<N_SLOTS; i++){
        mon -> BUFFER[i] = 0;
        mon -> slots[i] = Empty;
    }
}

void monitor_destroy(monitor_t *mon) {
    pthread_cond_destroy(&mon -> cv);
    pthread_mutex_destroy(&mon -> m);
}

void push_int(monitor_t *mon, int item) {
    pthread_mutex_lock(&mon -> m);
    mon -> n_w++;

    while((mon -> slots[0] == Full) && (mon -> slots[1] == Full)){
            pthread_cond_wait(&mon -> cv, &mon -> m);
    }

    if(mon -> slots[0] == Empty){
        mon -> BUFFER[0] = item;
        printf("P1 is writing: %d\n", item);
        mon -> slots[0] = Full;
    }
    else{
        mon -> BUFFER[1] = item;
        printf("P1 is writing: %d\n", item);
        mon -> slots[1] = Full;
    }
    mon -> n_w--;
    pthread_mutex_unlock(&mon -> m);
}

void push_pair(monitor_t *mon, int item1, int item2) {
    pthread_mutex_lock(&mon -> m);
    mon -> n_w++;

    while((mon -> slots[0] == Full) || (mon -> slots[1] == Full)){
        pthread_cond_wait(&mon -> cv, &mon -> m);
    }

    printf("P2 is writing: %d %d\n", item1, item2);
    mon -> BUFFER[0] = item1;
    mon -> slots[0] = Full;
    mon -> BUFFER[1] = item2;
    mon -> slots[1] = Full;

    mon -> n_w--;

    pthread_mutex_unlock(&mon -> m);
}

int fetch(monitor_t *mon) {
    pthread_mutex_lock(&mon -> m);
    int value;

    if((mon -> slots[0] == Empty) && (mon -> slots[1] == Empty)){
        value = 0;
    }
    else{
            if(mon -> slots[0] == Full){
                    value = mon -> BUFFER[0];
                    mon -> slots[0] = Empty;
                }
            else{
                value = mon -> BUFFER[1];
                mon -> slots[1] = Empty;
            }
            pthread_cond_signal(&mon -> cv);
            //pthread_cond_signal(&mon -> cv);
    }

    return value;
    pthread_mutex_unlock(&mon -> m);
}

// MAIN FUNCTION
int main(void) {

    pthread_t P1_id, P2_id, C_id;
    thread_name_t P1, P2, C;


    monitor_init(&mon);

    sprintf(P1,"t%d",0);
    pthread_create(&P1_id, NULL, &write_int, NULL);

    sprintf(P2,"t%d",1);
    pthread_create(&P2_id, NULL, &write_pair, NULL);

    sprintf(C,"t%d",2);
    pthread_create(&C_id, NULL, &read_buffer, NULL);


    pthread_join(P1_id, NULL);
    pthread_join(P2_id, NULL);
    pthread_join(C_id, NULL);

    monitor_destroy(&mon);

    return EXIT_SUCCESS;
}

// TYPE 1 THREAD LOOP
void *write_int(){
    FOREVER{
        spend_some_time(150);
        int item = 45;
        push_int(&mon, item);
    }
    pthread_exit(NULL);
}

void *write_pair(){
    FOREVER{
        spend_some_time(100);
        int item1, item2;
        item1 = 13;
        item2 = 65;
        push_pair(&mon, item1, item2);
    }
    pthread_exit(NULL);
}

void *read_buffer(){
    FOREVER{
        spend_some_time(40);
        int value = fetch(&mon);
        if(value == 0){
            printf("Empty buffer\n");
        }
        else{
            printf("Read value: %d\n", value);
        }
    }
    pthread_exit(NULL);
}

// AUXILIARY FUNCTIONS
double spend_some_time(int max_steps) {
    double x, sum=0.0, step;
    long i, N_STEPS=rand()%(max_steps*1000000);
    step = 1/(double)N_STEPS;
    for(i=0; i<N_STEPS; i++) {
        x = (i+0.5)*step;
        sum+=4.0/(1.0+x*x);
    }
    return step*sum;
}

【问题讨论】:

标签: c multithreading mutex condition-variable


【解决方案1】:

在函数 fetch 中,您在释放锁之前从 fetch 返回,因此即使您发出条件信号,其他线程也不会获得锁:

     return value;
     pthread_mutex_unlock(&mon -> m);

我不确定您是否需要在 fetch 中进行任何锁定,假设在内存中写入/读取 int 是原子的并且在 fetch 中您在将插槽设置​​为空之前读取缓冲区的值并且在推送函数中您将插槽设置​​为推入 int 后已满。 该练习表明,无论顺序是什么,您都需要获取一个 int,并且 fetch 不应该阻塞调用线程,因此它不应该等待任何推送函数完成来获取锁。 想象一下这种不使用任何锁定获取的场景:

  1. push_pair 正在设置缓冲区[0]
  2. push_pair 正在将 slot[0] 设置为满
  3. fetch 被调用看到 slot[0] 并获取 buffer[0]
  4. 获取信号,现在 push_int 已唤醒,但锁仍使用 push_pair,这导致 push_int 等待
  5. push_pair 正在设置缓冲区[1]
  6. push_pair 正在设置 slot[1]
  7. push_pair 放弃锁定
  8. push_int 现在可以获取锁并写入缓冲区[0]

在步骤 1-2 或 5-6 之间,fetch 不会得到值,因为它看到 slot[0] 或 slot[1] 为空。

您可以尝试考虑其他情况,看看不阻塞获取是否无害,也许我错过了一些东西。例如,即使在第 8 步中,如果 push_int 没有获得锁,而是 push_pair 再次获得了锁,它也不会写入任何内容,因为前一对的后半部分仍在缓冲区中。

不锁定 fetch 可以快速读取值,但有可能在发出推送请求后从 fetch 返回 0(空缓冲区),但我在问题中没有看到必须订购 fetch及时推送(推送时间之后的获取时间)。

【讨论】:

    猜你喜欢
    • 2010-11-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-01
    • 2015-06-19
    • 2018-10-06
    • 1970-01-01
    相关资源
    最近更新 更多