C 在pthreads中实现FIFO互斥

C 在pthreads中实现FIFO互斥,c,pthreads,mutex,C,Pthreads,Mutex,我试图实现一个支持并发插入(甚至在节点之间也可能发生)的二叉树,但不必为每个节点分配全局锁或单独的互斥锁。相反,分配的此类锁的数量应该与使用树的线程数量相同 因此,我最终遇到了一类问题。更简单地解释,这是当两个或多个线程执行以下操作时可能发生的情况: 1 for(;;) { 2 lock(mutex) 3 do_stuff 4 unlock(mutex) 5 } 1代表(;;){ 2锁(互斥) 3.做事 4解锁(互斥) 5 } 也就是说,如果线程1在一个“cpu突发”中执行指令4->

我试图实现一个支持并发插入(甚至在节点之间也可能发生)的二叉树,但不必为每个节点分配全局锁或单独的互斥锁。相反,分配的此类锁的数量应该与使用树的线程数量相同

因此,我最终遇到了一类问题。更简单地解释,这是当两个或多个线程执行以下操作时可能发生的情况:

1 for(;;) { 2 lock(mutex) 3 do_stuff 4 unlock(mutex) 5 } 1代表(;;){ 2锁(互斥) 3.做事 4解锁(互斥) 5 } 也就是说,如果线程1在一个“cpu突发”中执行指令4->5->1->2,那么线程2就会因执行不足而停止执行


另一方面,如果pthread中的互斥体有FIFO类型的锁定选项,那么这样的问题是可以避免的。那么,有没有办法在pthreads中实现FIFO类型的互斥锁呢?更改线程优先级可以做到这一点吗?

您可以查看
pthread\u mutexattr\u setprioceiling
函数

int pthread_mutexattr_setprioceiling
(
    pthread_mutexatt_t * attr, 
    int prioceiling,
    int * oldceiling
);
从文件中:

pthread_mutexattr_setprioceiling(3THR)设置互斥属性对象的优先级上限属性

attr指向由先前对pthread_mutexattr_init()的调用创建的互斥属性对象

prioceiling指定初始化互斥体的优先级上限。上限定义了执行互斥锁保护的关键部分的最低优先级。预处理将在SCHED_FIFO规定的最大优先级范围内。为了避免优先级反转,预处理将被设置为高于或等于可能锁定特定互斥体的所有线程的最高优先级

old天花板包含旧的优先级上限值


你可以这样做:

#include <pthread.h>

typedef struct ticket_lock {
    pthread_cond_t cond;
    pthread_mutex_t mutex;
    unsigned long queue_head, queue_tail;
} ticket_lock_t;

#define TICKET_LOCK_INITIALIZER { PTHREAD_COND_INITIALIZER, PTHREAD_MUTEX_INITIALIZER }

void ticket_lock(ticket_lock_t *ticket)
{
    unsigned long queue_me;

    pthread_mutex_lock(&ticket->mutex);
    queue_me = ticket->queue_tail++;
    while (queue_me != ticket->queue_head)
    {
        pthread_cond_wait(&ticket->cond, &ticket->mutex);
    }
    pthread_mutex_unlock(&ticket->mutex);
}

void ticket_unlock(ticket_lock_t *ticket)
{
    pthread_mutex_lock(&ticket->mutex);
    ticket->queue_head++;
    pthread_cond_broadcast(&ticket->cond);
    pthread_mutex_unlock(&ticket->mutex);
}
  • 定义一个“排队锁”,它由空闲/忙碌标志和pthread条件变量的链接列表组成。对排队_锁的访问受互斥锁保护

  • 要锁定排队的_锁,请执行以下操作:

    • 抓住互斥锁
    • 检查“忙”标志
    • 如果不忙;设置busy=true;释放互斥;完成
    • 如果忙;在队列末尾创建一个新条件并等待它(释放互斥)
  • 要解锁:

    • 抓住互斥锁
    • 如果没有其他线程排队,busy=false;释放互斥;完成
    • pthread_cond_用信号表示第一个等待条件
    • 不清除“忙碌”标志-所有权正在传递给其他线程
    • 释放互斥
  • 等待pthread_cond_信号解除阻止线程时:

    • 从队列头中删除我们的条件变量
    • 释放互斥

请注意,只有在更改排队锁的状态时,互斥锁才被锁定,而不是在排队锁保持的整个持续时间内。

您可以实现一个公平排队系统,其中每个线程在阻塞时都被添加到队列中,队列上的第一个线程在资源可用时始终获得资源。这种基于pthreads原语构建的“公平”票证锁可能如下所示:

#include <pthread.h>

typedef struct ticket_lock {
    pthread_cond_t cond;
    pthread_mutex_t mutex;
    unsigned long queue_head, queue_tail;
} ticket_lock_t;

#define TICKET_LOCK_INITIALIZER { PTHREAD_COND_INITIALIZER, PTHREAD_MUTEX_INITIALIZER }

void ticket_lock(ticket_lock_t *ticket)
{
    unsigned long queue_me;

    pthread_mutex_lock(&ticket->mutex);
    queue_me = ticket->queue_tail++;
    while (queue_me != ticket->queue_head)
    {
        pthread_cond_wait(&ticket->cond, &ticket->mutex);
    }
    pthread_mutex_unlock(&ticket->mutex);
}

void ticket_unlock(ticket_lock_t *ticket)
{
    pthread_mutex_lock(&ticket->mutex);
    ticket->queue_head++;
    pthread_cond_broadcast(&ticket->cond);
    pthread_mutex_unlock(&ticket->mutex);
}
#包括
typedef结构票据锁{
pthread_cond_t cond;
pthread_mutex_t mutex;
无符号长队列头、队列尾;
}车票锁;
#定义票据\锁\初始值设定项{PTHREAD\ COND\初始值设定项,PTHREAD\ MUTEX\初始值设定项}
无效票锁(票锁*票)
{
未签名的长队;
pthread_mutex_lock(&ticket->mutex);
queue_me=ticket->queue_tail++;
while(排队我!=车票->排队头)
{
pthread_cond_wait(&ticket->cond,&ticket->mutex);
}
pthread_mutex_unlock(&ticket->mutex);
}
无效车票解锁(车票锁定车票)
{
pthread_mutex_lock(&ticket->mutex);
票证->队列头++;
pthread_cond_广播(&ticket->cond);
pthread_mutex_unlock(&ticket->mutex);
}

您发布的示例没有解决方案。基本上,您只有一个关键部分,没有并行的地方

这就是说,您可以看到,将线程持有互斥锁的时间减少到最小非常重要,只需少量指令。这对于在动态数据结构(如树)中插入是困难的。概念上最简单的解决方案是每个树节点有一个读写锁


如果不希望每个树节点都有单独的锁,则可以在树的每个级别上有一个锁结构。我会用读写锁来做实验。在遍历树时,您可以使用手头节点的级别(加上下一个级别)的刚读锁定。然后,当您找到合适的方法插入该级别的锁进行写入时。

解决方案可能是使用。无锁定、无上下文切换、无休眠,并且比互斥体或条件变量快得多。原子操作并不是所有问题的最终解决方案,但我们已经创建了许多使用原子操作的通用数据结构的线程安全版本。他们跑得很快

原子操作是一系列简单的操作,如递增、递减或赋值,保证在多线程环境中以原子方式执行。如果两个线程同时命中op,cpu将确保一个线程一次执行op。原子操作是硬件指令,所以速度很快。“比较和交换”对于线程安全的数据结构非常有用。在我们的测试中,原子比较和交换大约与32位整数赋值一样快。也许慢了2倍。当你考虑用互斥体消耗多少CPU时,原子运算就快得多了。

用原子操作进行旋转以平衡树并非小事,但并非不可能。我在过去遇到过这个需求,并通过使线程安全来欺骗,因为使用原子操作可以很容易地完成skiplist。很抱歉,我不能给你一份我们的代码…我的公司会解雇我,但你自己做很容易

原子操作如何使数据结构无锁
TList *pOld, *pNew;
...
// allocate/fill/whatever to make pNew
...
while (1) { // concurrency loop
  pOld = _pHead;  // copy the state of the world. We operate on the copy
  pNew->pNext = pOld; // chain the new node to the current head of recycled items
  if (CAS(&_pHead, pOld, pNew))  // switch head of recycled items to new node
    break; // success
}
#if defined(_MSC_VER)
typedef volatile LONG Sync32_t;
#define SyncFetchAndIncrement32(V) (InterlockedIncrement(V) - 1)
#elif (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) > 40100
typedef volatile uint32_t Sync32_t;
#define SyncFetchAndIncrement32(V) __sync_fetch_and_add(V, 1)
#else
#error No atomic operations
#endif

class FairMutex {
private:
    Sync32_t                _nextTicket;
    Sync32_t                _curTicket;
    pthread_mutex_t         _mutex;
    pthread_cond_t          _cond;

public:
    inline FairMutex() : _nextTicket(0), _curTicket(0), _mutex(PTHREAD_MUTEX_INITIALIZER), _cond(PTHREAD_COND_INITIALIZER)
    {
    }
    inline ~FairMutex()
    {
        pthread_cond_destroy(&_cond);
        pthread_mutex_destroy(&_mutex);
    }
    inline void lock()
    {
        unsigned long myTicket = SyncFetchAndIncrement32(&_nextTicket);
        pthread_mutex_lock(&_mutex);
        while (_curTicket != myTicket) {
            pthread_cond_wait(&_cond, &_mutex);
        }
    }
    inline void unlock()
    {
        _curTicket++;
        pthread_cond_broadcast(&_cond);
        pthread_mutex_unlock(&_mutex);
    }
};