【问题标题】:Why using semaphores instead of condition variables hang sometimes in this problem?为什么在这个问题中有时使用信号量而不是条件变量?
【发布时间】:2020-10-28 11:30:26
【问题描述】:

我正在为an operating systems course 的作业编写代码。

This 是我正在编辑的文件。

This 是解决方案的测试代码。

注意:无需查看这些文件即可理解代码。

作业要求一种在线程之间进行通信的方式。线程有 3 种类型:malefemalematchmaker

我有 3 个函数:male(thread_id)female(thread_id)matchmaker(thread_id)

malefemalematchmaker 线程应该休眠,直到其他两种类型的两个线程可用。例如,如果没有单个female 线程在运行或没有单个matchmaker 线程在运行,则male 线程应该休眠。如果这三种类型都存在,matchmaker 将匹配 malefemale 线程,然后 3 个线程应该退出。

测试代码创建了 10 个 male 线程、10 个 female 线程和 10 个 matchmaker 线程,并期望每种类型中的一个能够一起通信然后退出。

注意:处理每种类型线程的函数代码几乎相同。

我使用条件变量解决了这个问题,它工作正常。这是解决方案:

struct lock *lock = NULL;

struct cv *male_cv = NULL;
struct cv *female_cv = NULL;
struct cv *matchmaker_cv = NULL;

int male_count = 0;
int female_count = 0;
int matchmaker_count = 0;

void whalemating_init() {
    lock = lock_create("whalemating lock");

    male_cv = cv_create("male");
    female_cv = cv_create("female");
    matchmaker_cv = cv_create("matchmaker");

    male_count = 0;
    female_count = 0;
    matchmaker_count = 0;
}

/*
 * Called by the driver during teardown.
 */

void
whalemating_cleanup() {

    lock_destroy(lock);
    lock = NULL;

    cv_destroy(male_cv);
    cv_destroy(female_cv);
    cv_destroy(matchmaker_cv);

    male_cv = NULL;
    female_cv = NULL;
    matchmaker_cv = NULL;
}

void
male(uint32_t index)
{
    (void)index;
    /*
     * Implement this function by calling male_start and male_end when
     * appropriate.
     */

    male_start(index);

    lock_acquire(lock);
    
    if (matchmaker_count > 0 && female_count > 0) {
        cv_signal(matchmaker_cv, lock);
        cv_signal(female_cv, lock);
    } else {
        male_count++;
        cv_wait(male_cv, lock);
        male_count--;
    }
        
    lock_release(lock);

    male_end(index);    

    return;
}

void
female(uint32_t index)
{
    (void)index;
    /*
     * Implement this function by calling female_start and female_end when
     * appropriate.
     */

    female_start(index);

    lock_acquire(lock);
    
    if (matchmaker_count > 0 && male_count > 0) {
        cv_signal(matchmaker_cv, lock);
        cv_signal(male_cv, lock);
    } else {
        female_count++;
        cv_wait(female_cv, lock);
        female_count--;
    }
        
    lock_release(lock);

    female_end(index);  

    return;
}

void
matchmaker(uint32_t index)
{
    (void)index;
    /*
     * Implement this function by calling matchmaker_start and matchmaker_end
     * when appropriate.
     */

    matchmaker_start(index);
    
    lock_acquire(lock);
    
    if (male_count > 0 && female_count > 0) {
        cv_signal(male_cv, lock);
        cv_signal(female_cv, lock);
    } else {
        matchmaker_count++;
        cv_wait(matchmaker_cv, lock);
        matchmaker_count--;
    }
        
    lock_release(lock);

    matchmaker_end(index);  

    return;
}

这是一个使用信号量但并不总是有效的信号量(在某些运行中运行良好并在其他运行中挂起):

struct semaphore *male_sem = NULL;
struct semaphore *female_sem = NULL;
struct semaphore *matchmaker_sem = NULL;

/*
 * Called by the driver during initialization.
 */

void whalemating_init() {
    // sem_create(sem_name, sem_initial_value);
    male_sem = sem_create("male", 0);
    female_sem = sem_create("female", 0);
    matchmaker_sem = sem_create("matchmaker", 0);
}

/*
 * Called by the driver during teardown.
 */

void
whalemating_cleanup() {
    sem_destroy(male_sem);
    sem_destroy(female_sem);
    sem_destroy(matchmaker_sem);

    male_sem = NULL;
    female_sem = NULL;
    matchmaker_sem = NULL;
}

void
male(uint32_t index)
{
    (void)index;
    /*
     * Implement this function by calling male_start and male_end when
     * appropriate.
     */

    male_start(index);

    V(male_sem);
    V(male_sem);
    P(female_sem);
    P(matchmaker_sem);

    male_end(index);    

    return;
}

void
female(uint32_t index)
{
    (void)index;
    /*
     * Implement this function by calling female_start and female_end when
     * appropriate.
     */

    female_start(index);
    
    V(female_sem);
    V(female_sem);
    P(male_sem);
    P(matchmaker_sem);

    female_end(index);  

    return;
}

void
matchmaker(uint32_t index)
{
    (void)index;
    /*
     * Implement this function by calling matchmaker_start and matchmaker_end
     * when appropriate.
     */

    matchmaker_start(index);

    V(matchmaker_sem);
    V(matchmaker_sem);
    P(male_sem);
    P(female_sem);

    matchmaker_end(index);  

    return;
}

P 函数接受一个信号量,如果大于 0,则减少其计数,如果计数为 0,则休眠。

V 函数增加计数,如果有任何休眠线程,则唤醒。

在后一种解决方案中,每个线程都会将其信号量增加两次,因为其他两个线程中的每一个都会将其减少一次。然后在其他两个信号量上分别调用P。如果两个信号量的计数都大于 0,则函数继续执行直到结束。如果其中至少一个的计数为 0,则线程会休眠,直到该信号量的计数上升。

后一种方案有什么问题?


编辑

这个带有信号量的代码可以正常工作:

struct semaphore *male_sem = NULL;
struct semaphore *female_sem = NULL;
struct semaphore *matchmaker_sem = NULL;

/*
 * Called by the driver during initialization.
 */

void whalemating_init() {
    // sem_create(sem_name, sem_initial_value);
    male_sem = sem_create("male", 0);
    female_sem = sem_create("female", 0);
    matchmaker_sem = sem_create("matchmaker", 0);

}

/*
 * Called by the driver during teardown.
 */

void
whalemating_cleanup() {
    sem_destroy(male_sem);
    sem_destroy(female_sem);
    sem_destroy(matchmaker_sem);

    male_sem = NULL;
    female_sem = NULL;
    matchmaker_sem = NULL; 
}

void
male(uint32_t index)
{
    (void)index;
    /*
     * Implement this function by calling male_start and male_end when
     * appropriate.
     */

    male_start(index);

    P(male_sem);
    V(matchmaker_sem);

    male_end(index);    

    return;
}

void
female(uint32_t index)
{
    (void)index;
    /*
     * Implement this function by calling female_start and female_end when
     * appropriate.
     */

    female_start(index);
    
    P(female_sem);
    V(matchmaker_sem);

    female_end(index);  

    return;
}

void
matchmaker(uint32_t index)
{
    (void)index;
    /*
     * Implement this function by calling matchmaker_start and matchmaker_end
     * when appropriate.
     */

    matchmaker_start(index);

    V(male_sem);
    V(female_sem);
    P(matchmaker_sem);
    P(matchmaker_sem);

    matchmaker_end(index);  

    return;
}

为什么这个有效而另一个无效?

【问题讨论】:

  • 这与问题没有直接关系,但与程序逻辑有关:我认为matchmaker 不需要 10 个线程。 matchmaker 应该只有一个线程,它应该 (1) 唤醒,(2) 进行匹配,如果存在空闲对,(3) 否则,休眠...
  • @ssd 感谢您的建议。不幸的是,这就是他们决定测试代码的方式。我对此无能为力。
  • @ssd 我猜这是一个关于同步 3 个线程的任务,而男/女/媒人的名字只是愚蠢的名字。如果你愿意,可以称它们为线程 A、B、C。

标签: c multithreading synchronization semaphore condition-variable


【解决方案1】:

在工作信号量解决方案中,永远不会出现这样的情况,即媒人离开信号量访问而没有至少一名男性和一名女性也留下同步代码:当媒人从他的第二个P(matchmaker_sem)返回时,保证成为一男一女,度过最后的V(matchmaker_sem),这样媒人就可以施展魔法了。

您的解决方案不能这样说: 一旦一个男性发出了他的第一个V(male_sem) 和一个女性的V(female_sem) 信号,媒人就可以通过他的两个必要的P(male_sem) P(female_sem) 并退出,而男性和女性仍然在同步代码中徘徊。虽然信号量访问的总计数被取消,所有女性、男性和媒人最终都会终止,但它们不会以任何同步方式这样做,即它们的状态与任何预期的时间序列不匹配,并且同步代码只是一个任意保持-up 的一些功能。我认为这些函数之外的代码会停止程序的执行,而不是等待信号量。

PS:如果 xy_end(index) 函数应该指示三个参与者中必要的状态变化,那么第二个代码也会被破坏并执行,因为调度程序只有很小的机会到达 V(matchmaker_sem)female/male_end(index).

PPS:也许你的误解源于V(semaphore)。该指令不会阻塞!

【讨论】:

  • 感谢您的回答。我不太明白第二个代码的问题。你有什么建议不要破坏它吗?还是在这种情况下我根本不应该使用信号量?
  • 当调度器放置例如在此过程执行 V(matchmaker_sem) 但在它可以执行 female_end(index) 并将媒人设置为 RUNNING 之前处于 WAITING 状态的女性,那么这取决于您对女性状态的期望,如果您的程序是正确的或不是。将female_end(index) 调用移到V(matchmaker_sem) 前面也无济于事,因为那样你又会处于不一致的状态。用相互信息交换(仅仅是它们的身份)来装饰单个线程需要的不仅仅是这个简单的同步方案所能支持的。
  • 换句话说:如果媒人是一个打开一男一女可以通过的门进行交配的实体,如果这个媒人打开了门恰好是之前碰巧在他身上调用V(matchmaker_sem) 的两个进程。他只知道门前至少有一男一女等着。
  • 所以我认为使用信号量不是一个好主意,对吧?另外我认为我提供的带有条件变量的代码也被破坏了,我想......我想我应该在释放锁之前移动 XX_end(id) 调用。我说的对吗?
猜你喜欢
  • 2011-10-19
  • 2019-02-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多