如何锁定队列变量地址而不是使用临界区?

Posted

技术标签:

【中文标题】如何锁定队列变量地址而不是使用临界区?【英文标题】:How to lock Queue variable address instead of using Critical Section? 【发布时间】:2010-03-03 04:27:55 【问题描述】:

我有 2 个线程和全局队列,一个线程 (t1) 推送数据,另一个线程 (t2) 弹出数据,我想在不使用函数的情况下同步此操作,我们可以使用 Windows API 使用带有关键部分的队列.

Queue是全局的,想知道怎么同步,是不是通过Queue的锁定地址来完成的?

是否可以使用 Boost Library 来解决上述问题?

【问题讨论】:

您不想要暴击部分的任何具体原因?它是最快的 afaik。 我想锁定它的地址,以便另一个线程可以在弹出之前等待。使用临界区发生了一些问题,因此决定自行锁定变量。 :) 也许最好询问如何修复使用关键部分的队列。无锁队列当然是可能的,但编写正确的无锁代码比使用锁的正确代码要困难得多 【参考方案1】:

一种方法是使用两个队列而不是一个:

生产者线程将项目推送到队列 A。 当消费者线程想要弹出项目时,队列 A 与空队列 B 交换。 生产者线程继续将项目推送到新队列 A。 消费者不间断地从队列 B 中消费项目并将其清空。 队列 A 与队列 B 等交换。

唯一的锁定/阻塞/同步发生在交换队列时,这应该是一个快速的操作,因为它实际上是交换两个指针的问题。

【讨论】:

【参考方案2】:

我认为您可以在不使用任何原子或任何线程安全的东西的情况下创建具有这些条件的队列?

如果它只是一个圆形缓冲区,一个线程控制读取指针,另一个控制写入指针。在完成阅读或写作之前,两者都不会更新。它只是工作?

唯一的困难在于确定何时 read==write 队列是满的还是空的,但您可以通过在队列中始终保留一个虚拟项目来克服这个问题

class Queue

     volatile Object* buffer;
     int size;
     volatile int readpoint;
     volatile int writepoint;

     void Init(int s)
     
          size = s;
          buffer = new Object[s]; 
          readpoint = 0;
          writepoint = 1;
     

     //thread A will call this
     bool Push(Object p)
     
         if(writepoint == readpoint)
         return false;
         int wp = writepoint - 1;
         if(wp<0)
             wp+=size;
         buffer[wp] = p;
         int newWritepoint = writepoint + 1;
         if(newWritepoint==size)
            newWritePoint = 0;
         writepoint = newWritepoint;
         return true;
      

      // thread B will call this
      bool Pop(Object* p)
      
          writepointTest = writepoint;
          if(writepointTest<readpoint)
               writepointTest+=size;
          if(readpoint+1 == writepoint)
              return false;
          *p = buffer[readpoint];

         int newReadpoint = readpoint + 1;
         if(newReadpoint==size)
            newReadPoint = 0;
         readpoint = newReadPoint;
         return true;
      
;

【讨论】:

写一个无锁队列是可能的——但这不是小事。请参阅:drdobbs.com/cpp/210604448 示例。 @Jerry 是的,我与人共同编写了一个无锁队列,但它们仍然有点“锁定”并使用原子。如果在他指定的情况下使用,上面应该可以工作并且完全无锁和线程安全,不是吗?如果上述方法适用于他想要的东西,那么没有理由制作一个更普遍的线程安全的适当的无锁队列?我很想知道上面的代码是否 == 失败。 可能在某些条件下它会起作用,但您需要一些明确的约束(超出他指定的范围)。例如,似乎要求读取和写入 int 是隐式原子的。如果 Pop() 在 pop 写入的同时从写入点读取,并且读取/写入不是原子的,那么它可能/将会有一个严重的问题。【参考方案3】:

处理此问题的另一种方法是动态分配队列并将其分配给指针。当项目必须出列时,指针值在线程之间传递,并且您使用临界区保护此操作。这意味着每次推送到队列时都会锁定,但对删除项目的争用要少得多。

当您在入队和出队之间有很多项目时,这很有效,而当项目很少时效果不佳。

示例(我正在使用一些给定的 RAII 锁定类来进行锁定)。另请注意...只有当只有一个线程出队时才真正安全。

queue* my_queue = 0;
queue* pDequeue = 0;
critical_section section;

void enqueue(stuff& item)

   locker lock(section);
   if (!my_queue)
   
      my_queue = new queue;
   
   my_queue->add(item);


item* dequeue()

   if (!pDequeue)
     //handoff for dequeue work
      locker lock(section);
      pDequeue = my_queue;
      my_queue = 0;
   
   if (pDequeue)
   
      item* pItem = pDequeue->pop(); //remove item and return it.
      if (!pItem)
      
         delete pDequeue;
         pDequeue = 0;
      
      return pItem;
   
   return 0;

【讨论】:

以上是关于如何锁定队列变量地址而不是使用临界区?的主要内容,如果未能解决你的问题,请参考以下文章

临界区是不是确保由一个线程修改的任何数据类型的共享变量对其他线程可见? [复制]

关键部分所有权

完成临界区互斥的根本办法

uc/os进中断与进临界区有啥区别?

线程同步(windows平台):临界区

关于C++临界区CriticalSection的问题。