【问题标题】:Problem with cond.signal or lock.release?cond.signal 或 lock.release 有问题?
【发布时间】:2023-03-17 03:17:01
【问题描述】:

嗨 当我运行以下代码时,我发现信号线程在另一个线程启动之前继续运行了很长时间......这是为什么?信号器释放锁后,唤醒的线程不应该立即运行吗?或者操作系统是否需要很长时间才能将睡眠线程放回就绪队列?

#include pthread.h

#include stdio.h

#include stdlib.h

void stupidfunction1(void *arg);

void stupidfunction2(void *arg);

pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;

pthread_cond_t cond1 = PTHREAD_COND_INITIALIZER;

int thread1count,thread2count;

int thread1waiting = 0;

int thread2waiting = 0;

void main()

{
    printf("Hello World\n");

    pthread_t thread1,thread2;

    int i;

    thread1count = 0;

    thread2count = 0;


    i = pthread_create(&thread1,NULL,&stupidfunction1,NULL);

    i = pthread_create(&thread2,NULL,&stupidfunction2,NULL);

    pthread_join(thread1,NULL);

    pthread_join(thread2,NULL);

    printf("Done with everythinh");


}


void stupidfunction1(void *arg)

{
int i = 0;

    for(i = 0;i<50;i++)
    {

    thread1count++;

    pthread_mutex_lock(&mutex1);

    if((thread1count-thread2count)>5)

    {
           thread1waiting = 1;

               printf("thread1 waiting \n");    

               pthread_cond_wait(&cond1,&mutex1);

               thread1waiting = 0;
    }

        else if((thread2waiting == 1) && abs(thread1count-thread2count)<1)

    {

        printf("signalling thread2\n");

        pthread_cond_signal(&cond1);

    }

    pthread_mutex_unlock(&mutex1);

    printf("Hey its thread 1 @  %d\n",thread1count);
    }
}


void stupidfunction2(void *arg)
{
int i = 0;

    for(i = 0;i<50;i++)
    {

    thread2count++;

    pthread_mutex_lock(&mutex1);

    if((thread2count-thread1count)>5)
    {
               thread2waiting = 1;

           printf("thread2 waiting \n");    

               pthread_cond_wait(&cond1,&mutex1);

               thread2waiting = 0;
    }

        else if((thread1waiting == 1) && abs(thread1count-thread2count)<1)
    {

        printf("signalling thread1\n");

        pthread_cond_signal(&cond1);
    }

    pthread_mutex_unlock(&mutex1);

    printf("Hey its thread 2 @  %d\n",thread2count);
    }
}

输出:

Hey its thread 2 @  1

Hey its thread 2 @  2

Hey its thread 2 @  3

Hey its thread 2 @  4

Hey its thread 2 @  5

thread2 waiting 

Hey its thread 1 @  1

Hey its thread 1 @  2

Hey its thread 1 @  3

Hey its thread 1 @  4

Hey its thread 1 @  5

signalling thread2

Hey its thread 1 @  6

Hey its thread 1 @  7

Hey its thread 1 @  8

Hey its thread 1 @  9

Hey its thread 1 @  10

Hey its thread 1 @  11

【问题讨论】:

    标签: c multithreading locking pthreads signals


    【解决方案1】:

    直接回答您的问题:不,pthread_mutex_unlockpthread_cond_signal 不要立即唤醒任何等待线程。相反,他们很可能只是将其标记为“准备运行”,然后操作系统会在需要时安排唤醒线程。当然,操作系统可能决定立即切换到该线程(特别是如果它的优先级高于任何当前正在执行的线程),但它可能不会。

    但是,无论如何,您的代码可能无法正常运行:您可能同时运行两个线程!

    仅仅因为pthread_cond_wait返回,并不意味着条件变量已经发出信号。这被称为“虚假唤醒”。要正确使用pthread_cond_wait,您必须将其放入一个循环中,在该循环中测试与唤醒相关的条件,同时保持互斥锁,然后立即调用pthread_cond_wait。例如

    void wait_until_signalled(int* wake_flag,pthread_cond_t* cond,pthread_mutex_t* mutex)
    {
        pthread_mutex_lock(&mutex);
        while(!(*wake_flag)) /* if the int pointed to by wake_flag is non-zero then wake up */
        {
            pthread_cond_wait(&cond,&mutex);
        }
        pthread_mutex_unlock(&mutex);
    }
    
    void signal(int* wake_flag,pthread_cond_it* cond,pthread_mutex_t* mutex)
    {
        pthread_mutex_lock(&mutex);
        *wake_flag=1; /* tell the waiting thread that it should wake */
        pthread_mutex_unlock(&mutex);
        pthread_cond_signal(&cond); /* wake up the thread if it is blocked in pthread_cond_wait*/
    }
    

    当然,您可能想检查pthread_xxx 调用的返回值。

    由于wake_flag所指向的值只是在互斥锁被锁定的情况下才被检查和修改,那么如果设置了等待线程肯定会被唤醒,并且在标志被设置之前不会从wait_until_signalled返回。对pthread_cond_wait 的调用以原子方式将线程标记为等待,并解锁互斥锁,因此对pthread_cond_signal 的调用将看到线程正在等待,并唤醒它以便它可以检查标志(已设置),或者线程没有等待,这意味着它必须在signal中的线程设置标志之后锁定互斥锁,在这种情况下等待线程将看到标志设置并返回。

    【讨论】:

      【解决方案2】:

      线程在调度程序开始运行时运行。

      方式 1 线程 2 不会快速运行:当线程 1 通知线程 2 唤醒时(在您的情况下,在线程 2 实际唤醒之前很多次)所有等待条件变量的线程必须在调度程序时争夺锁运行它们。在您的示例中,我打赌调度程序在线程 1 完成之前永远不会到达线程 2。一个不错的选择是您的时间片是 1 毫秒。

      方式两种线程 2 运行不快:线程 1 向线程 2 发出信号,但是当线程 2 唤醒时,线程 1 仍然有锁(因为线程 1 处于稍后的循环中,或者因为线程 2 立即唤醒并且线程 1 具有互斥锁在线程 1 调用 cond_signal 之前被锁定),因此线程 2 现在作为互斥体上的服务员重新进入睡眠状态。

      仍然假设线程 2 是等待线程。

      假设您希望线程 2 立即唤醒...AFAIK,您无能为力。

      如果您真的希望线程 1 在发出信号后停止并且您仍想使用 pthread_cond(还有其他更快的方法!),您可以在发出信号之前解锁互斥锁 (this allowed i think),然后调用 sched_yield() 或 nanosleep ({0,0}) 就在信号之后。对性能不利,因为线程 1 只是将自己置于等待运行的线程行的后面(具有相同的优先级)。这将增加上下文切换与运行时的比率。这也很糟糕,因为您必须做额外的解锁/锁定。最重要的是,线程 1 可能会在线程 2 之前再次唤醒!您必须使用循环,不断发出信号并让步,直到该死的线程 2 完成它的工作!笨拙!

      顺便说一下,我对 volatile 的解释是它告诉编译器该变量会随时更改。出于性能原因,在函数开始时将该变量复制到寄存器中是不好的。编译器很清楚哪些变量可以做到这一点,但它有时会出错。我想说的是,如果 gcc 在寄存器中缓存了任何共享变量,那么当打开优化时 volatile 可能 会产生影响。我不知道 gcc 是否在寄存器中缓存全局变量。

      干杯。

      【讨论】:

        【解决方案3】:

        我可以在这里看到两个错误:

        • 您没有锁定 pthread_cond_signal 周围的互斥锁。这意味着信号可以在另一个线程在检查它是否应该继续运行和它进入pthread_cond_wait之间的时间发送,因此信号可能会在这里丢失。

        • 共享变量应该是易失的。编译器可能决定将访问移到循环之外,或者部分展开循环并省略循环体的两个实例之间的访问。

        这些都不能直接解释您所看到的症状;但是给定的行为绝对在标准的允许范围内。

        【讨论】:

        • 我不明白第一个错误 - 当第一个线程正在检查其条件时,另一个线程不能处于临界区,那么第一个线程怎么会错过信号呢?行为在允许范围内是什么意思? cond.signal 不应该立即唤醒并放入就绪队列或锁定队列。
        • volatile 不是正确的 pthreads 代码所必需的。必要的同步函数(例如pthread_mutex_lock())意味着编译器障碍。
        猜你喜欢
        • 1970-01-01
        • 2013-03-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-02-10
        • 2021-02-07
        • 2011-01-21
        • 1970-01-01
        相关资源
        最近更新 更多