0

谁能建议如何使用 pthread_mutex_lock 优化下面的应用程序代码?

让我描述一下情况:
我有 2 个线程共享一个全局共享内存变量。该变量shmPtr->status在两个函数中都受到互斥锁的保护。虽然sleep(1/2)task1 函数中有一个“for 循环”,但我无法shmPtr->status在需要时访问 task2 中的,必须等到 task1 函数中的“for 循环”完成。大约需要 50 秒shmPtr->status才能使用 task2 功能。

我想知道为什么 task1 函数没有释放互斥锁,尽管这sleep(1/2)条线。我不想等待shmPtr->status在 task2 函数中处理。请指教。

 thr_id1 = pthread_create ( &p_thread1, NULL, (void *)execution_task1, NULL );
 thr_id2 = pthread_create ( &p_thread2, NULL, (void *)execution_task2, NULL );  

 void execution_task1()
 {
     for(int i = 0;i < 100;i++) 
     {
          //100 lines of application code running here
          pthread_mutex_lock(&lock);  
                shmPtr->status = 1;  //shared memory variable 
         pthread_mutex_unlock(&lock);
         sleep(1/2);
     }
 }

 void execution_task2()
 {
         //100 lines of application code running here
         pthread_mutex_lock(&lock);  
                shmPtr->status = 0;  //shared memory variable
         pthread_mutex_unlock(&lock);
         sleep(1/2);
 }

问候, NK

4

1 回答 1

0

我想知道为什么 task1 函数即使在睡眠(1/2)的情况下也不会释放互斥锁。

没有理由认为execution_task1()在您的示例中运行的线程未能释放互斥锁,但您可以确定是否正确测试了其pthread_mutex_unlock()调用的返回值。相反,潜在的问题是它在任何其他竞争它的线程有机会获得它之前重新获得互斥体。

sleep(1/2)调用 to 似乎无法有效防止这种情况 发生,这似乎是合理的。1/2是一个整数除法,计算结果为 0,因此您正在执行sleep(0). 这可能根本不会导致调用线程挂起,甚至可能不会导致线程让出 CPU。

更一般地说,对于任何线程同步问题,sleep()都不是一个好的解决方案。

但是,如果您在多核系统上运行,即使您不是,那么如果函数在释放互斥锁和尝试之间确实执行了一百行代码,那么通过这种机制冻结其他线程似乎不太可能再次锁定它。如果那是你认为你看到的,那就更努力地看。

如果您确实需要强制一个线程让其他人有机会获得互斥锁,那么您也许可以设置一个公平排队系统,如在 pthreads 中实现 FIFO 互斥锁中所述。但是,对于您描述的情况,一个长时间运行的线程偶尔需要让步给其他更快的任务,您可以考虑引入一个条件变量,该长时间运行的线程可以在该变量上挂起,以及一个原子计数器。可以确定是否应该这样做:

#include <stdatomic.h>

// ...

pthread_cond_t yield_cv = PTHREAD_COND_INITIALIZER;
_Atomic unsigned int waiting_thread_count = ATOMIC_VAR_INIT(0);

void execution_task1() {
    for (int i = 0; i < 100; i++) {
        // ...

        pthread_mutex_lock(&lock);
        if (waiting_thread_count > 0) {
            pthread_cond_wait(&yield_cv, &lock);
            // spurrious wakeup ok
        }

        // ... critical section ...

        pthread_mutex_unlock(&lock);
    }
}

void execution_task2() {
    // ...

    waiting_thread_count += 1;  // Atomic get & increment; safe w/o mutex
    pthread_mutex_lock(&lock);  
    waiting_thread_count -= 1;
    pthread_cond_signal(&yield_cv);  // no problem if no-one is presently waiting

    // ... critical section ...

    pthread_mutex_unlock(&lock);
}

使用原子计数器使程序不必用自己的互斥锁保护该计数器,这可能只是转移问题而不是解决问题。这允许线程使用计数器来表示即将尝试获取互斥锁的信号。然后,此意图对其他线程可见,因此它可以在 CV 上挂起以允许其他线程获取互斥锁。

然后,短期运行的线程通过递减计数器来确认获取互斥锁。他们必须在释放互斥体之前这样做,否则长时间运行的线程可能会循环,获取互斥体,并在递减之前读取计数器,从而在无法预期额外信号时错误地阻塞 CV。

尽管 CV 可能会受到虚假唤醒的影响,但这对这种方法来说并不是一个严重的问题。如果长时间运行的线程在 CV 上等待时被虚假唤醒,那么最糟糕的情况是它对其主循环执行了一次迭代,然后再次等待。

于 2020-09-03T15:30:58.587 回答