Pthreads:共享内存监视器设计

Pthreads: shared memory monitor design

我正在做一个作业,我们正在创建 4 个共享内存的线程。只是想问一下我的监视器的设计是否看起来不错,因为当我尝试取消所有线程时,我的代码中的某个地方出现了死锁/停顿。

之前,我已经确定了在线程被取消时锁定互斥锁的线程停滞,导致线程在等待互斥锁解锁时陷入死锁。我已经实施了一些更改,但当我使用

将一些数据传输到其中时似乎仍然停滞不前
cat input.txt |./app

但是,如果我使用 getline() 直接从文件中读取数据,那么它不会停止并且所有线程都被取消。

目前,监视器包含 2 个共享列表(它们是使用相同的节点池创建的),一个互斥锁控制对该列表的访问,每个列表有 4 个条件变量 2。

//shared data
static List *sendList;
static List *receivelist;

//locks
static pthread_cond_t sendListIsFullCondVar = PTHREAD_COND_INITIALIZER;
static pthread_cond_t sendListIsEmptyCondVar = PTHREAD_COND_INITIALIZER;
static pthread_cond_t receiveListIsFullCondVar = PTHREAD_COND_INITIALIZER;
static pthread_cond_t receiveListIsEmptyCondVar = PTHREAD_COND_INITIALIZER;

static pthread_mutex_t listAccessMutex = PTHREAD_MUTEX_INITIALIZER;

监控界面由每个列表的添加函数和获取函数组成。每个线程都应该添加到列表或从列表中获取,但不能同时添加。

更具体地说,keyboardThread将数据放入sendlist,sendThread从sendlist获取数据,receiveThread将数据放入receivelist,printThread从receivelist中获取数据。

void Monitor_sendlistAdd(void *item)
{

    pthread_mutex_lock(&listAccessMutex);

    if (List_count(sendList) == MAX_LIST_SIZE)
    {
        pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
        pthread_cond_wait(&sendListIsFullCondVar, &listAccessMutex);
    }
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);

    List_prepend(sendList, item);
    pthread_cond_signal(&sendListIsEmptyCondVar);
    pthread_mutex_unlock(&listAccessMutex);

    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
}

void *Monitor_sendlistGet()
{
    pthread_mutex_lock(&listAccessMutex);
    if (List_count(sendList) == 0)
    {
        pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
        pthread_cond_wait(&sendListIsEmptyCondVar, &listAccessMutex);
    }
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);

    void *item = List_trim(sendList);

    pthread_cond_signal(&sendListIsFullCondVar);
    pthread_mutex_unlock(&listAccessMutex);
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
    return item;
}

(我没有包含 receiveList 的接口,因为它是相同的。)

我正在更改 if 语句之后的取消状态,以确保在锁定互斥锁时线程不会被取消,如果它正在等待互斥锁,这将阻止任何进程取消。

我还为每个线程提供了一个释放其互斥量的清除处理程序,再次确保没有线程在不保持互斥量锁定的情况下取消。

void *keyboardThread()
{
    pthread_cleanup_push(Monitor_releaseListAccessMutex, NULL);
    while (1)
    {
     ... some codes
    }
    pthread_cleanup_pop(1);
    return;

所以,是的,我完全不知道我的代码中还有哪些地方可能会被阻塞。其余代码只是连接到套接字以在端口和 malloc 之间发送数据。我查看了手册,似乎 mutex_lock 是我代码中唯一可以阻止线程取消的函数。

如果可能的话,不要使用线程取消。它有与之相关的深层问题。

要从 pthread_cond_wait() 中断开线程,请使用 pthread_cond_signal()pthread_cond_broadcast()。或者首先使用 pthread_cond_timedwait() 并让超时到期。

“但是,等等!”我想你会说,“那么我的线程就会像收到正常信号一样继续运行!”还有一个问题:你的线程无论如何都需要能够处理来自等待的虚假 returns,因为这些可能并且确实会发生。他们必须在等待之前检查是否需要等待,然后他们必须再次检查,并可能在等待结束后再次等待。

然后,您可以做的是添加一个共享标志,通知您的线程它们应该中止而不是正常进行,并让它们检查它是否未设置为等待条件之一。如果它 在等待循环的任何迭代之前设置,那么线程应该采取任何适当的操作,例如(释放所有锁定的互斥锁和)终止。

您备注:

I also giving each thread's a cleaup handler that releases its mutex

这可能不是个好主意,它可能会直接导致您的问题。线程不得尝试解锁它们未锁定的互斥量,因此为了使清理处理程序正常工作,您需要跟踪每个线程当前已锁定的互斥量,以清理处理程序可以采取的形式。可以想象您可以这样做,但充其量它会很混乱,而且可能很脆弱,而且它很可能会带来自己的同步问题。这是使用线程取消的问题之一。