pthread_rwlock有多少个同时读者?

时间:2012-08-08 14:17:20

标签: c++ linux multithreading pthreads

我有一个多线程应用程序,它创建了48个线程,所有这些线程都需要访问公共属性(stl :: map)。地图将仅在线程开始时写入,其余时间将从中读取地图。这似乎是pthread_rw_lock的完美用例,并且似乎都运行良好。

我遇到了一个完全不相关的seg-fault并开始分析核心。使用gdb,我执行了命令info threads,并对结果感到非常惊讶。我观察到几个线程实际上是按照预期从地图中读取的,但奇怪的是,在pthread_rwlock_rdlock()中等待rw_lock的几个线程被阻塞了。

以下是等待锁定的线程的堆栈跟踪:

#0  0xffffe430 in __kernel_vsyscall ()
#1  0xf76fe159 in __lll_lock_wait () from /lib/libpthread.so.0
#2  0xf76fab5d in pthread_rwlock_rdlock () from /lib/libpthread.so.0
#3  0x0804a81a in DiameterServiceSingleton::getDiameterService(void*) ()

有这么多线程,很难说有多少是在阅读,有多少被阻止,但我不明白为什么 任何 线程会被阻止等待阅读,考虑到其他线程已经在阅读。

所以这是我的问题:当其他线程已经从中读取时,为什么有些线程被阻塞等待读取rw_lock?似乎可以同时读取的线程数有限制。

我看过pthread_rwlock_attr_t函数并没有看到任何相关内容。

操作系统是Linux,SUSE 11。

以下是相关代码:

{
  pthread_rwlock_init(&serviceMapRwLock_, NULL);
}

// This method is called for each request processed by the threads
Service *ServiceSingleton::getService(void *serviceId)
{
  pthread_rwlock_rdlock(&serviceMapRwLock_);
    ServiceMapType::const_iterator iter = serviceMap_.find(serviceId);
    bool notFound(iter == serviceMap_.end());
  pthread_rwlock_unlock(&serviceMapRwLock_);

  if(notFound)
  {
    return NULL;
  }

  return iter->second;
}

// This method is only called when the app is starting
void ServiceSingleton::addService(void *serviceId, Service *service)
{
  pthread_rwlock_wrlock(&serviceMapRwLock_);
    serviceMap_[serviceId] = service;
  pthread_rwlock_unlock(&serviceMapRwLock_);
}

更新

正如MarkB的评论中所提到的,如果我设置了pthread_rwlockattr_getkind_np()来优先考虑编写者,并且有一个编写器被阻塞等待,那么观察到的行为就有意义了。但是,我使用默认值,我认为是优先考虑读者。我刚刚确认有 没有 线程被阻止等待写入。我还在评论中更新了@Shahbaz建议的代码并得到了相同的结果。

2 个答案:

答案 0 :(得分:6)

您只是观察了获取锁定所涉及的固有性能问题。这需要一些时间,你恰好在它的中间抓住那些线程。当受锁保护的操作持续时间非常短时,尤其如此。

  

编辑:阅读来源,glibc使用lll_lock来保护其自己的pthread库数据结构中的关键部分。 pthread_rwlock_rdlock检查几个标志并递增计数器,因此它在持有锁时执行这些操作。完成后,将使用lll_unlock释放锁定。

为了演示,我实现了一个在获取rwlock后休眠的短程序。主线程等待它们完成。但在等待之前,它会打印出线程所实现的并发性。

enum { CONC = 50 };

pthread_rwlock_t rwlock;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
unsigned count;

void *routine(void *arg)
{
    int *fds = static_cast<int *>(arg);
    pthread_rwlock_rdlock(&rwlock);
    pthread_mutex_lock(&mutex);
    ++count;
    if (count == CONC) pthread_cond_signal(&cond);
    pthread_mutex_unlock(&mutex);
    sleep(5);
    pthread_rwlock_unlock(&rwlock);
    pthread_t self = pthread_self();
    write(fds[1], &self, sizeof(self));
    return 0;
}

主线程等待计数器达到50:

int main()
{
    int fds[2];
    pipe(fds);
    pthread_rwlock_init(&rwlock, 0);
    pthread_mutex_lock(&mutex);
    for (int i = 0; i < CONC; i++) {
        pthread_t tid;
        pthread_create(&tid, NULL, routine, fds);
    }
    while (count < CONC) pthread_cond_wait(&cond, &mutex);
    pthread_mutex_unlock(&mutex);
    std::cout << "count: " << count << std::endl;
    for (int i = 0; i < CONC; i++) {
        pthread_t tid;
        read(fds[0], &tid, sizeof(tid));
        pthread_join(tid, 0);
    }
    pthread_rwlock_destroy(&rwlock);
    pthread_exit(0);
}

编辑:使用C ++ 11线程支持简化示例:

enum { CONC = 1000 };
std::vector<std::thread> threads;

pthread_rwlock_t rwlock;
std::mutex mutex;
std::condition_variable cond;
unsigned count;

void *routine(int self)
{
    pthread_rwlock_rdlock(&rwlock);
    { std::unique_lock<std::mutex> lk(mutex);
      if (++count == CONC) cond.notify_one(); }
    sleep(5);
    pthread_rwlock_unlock(&rwlock);
    return 0;
}

int main()
{
    pthread_rwlock_init(&rwlock, 0);
    { std::unique_lock<std::mutex> lk(mutex);
      for (int i = 0; i < CONC; i++) {
          threads.push_back(std::thread(routine, i));
      }
      cond.wait(lk, [](){return count == CONC;}); }
    std::cout << "count: " << count << std::endl;
    for (int i = 0; i < CONC; i++) {
        threads[i].join();
    }
    pthread_rwlock_destroy(&rwlock);
    pthread_exit(0);
}

答案 1 :(得分:3)

作为旁注,上面发布的代码已被破坏。 你不能在rw_lock'd部分中访问iter-&gt;第二,因为只要解锁rw_lock,编写器就可以删除地图中的任何元素,从而使其上的任何迭代器无效。

我知道你不是在你的情况下这样做,因为你没有在程序执行开始之后写任何东西,但仍然值得一提。

另外,作为旁注,由于您描述的行为似乎是序列化的(编写者在开始时写入地图,然后读者从现在开始读取“只读”地图),您可能应该像这样:

int writerDoneWithMap = 0;
// pthread_cond & mutex init here

// The writer write to the map here 

// Then they signal the reader that they are done with it
while (!__sync_bool_compare_and_swap(&writerDoneWithMap, 1, writerDoneWithMap));
pthread_cond_broadcast here


// The readers will then simply do this:
while (!writerDoneWithMap) 
{
     // pthread_cond_wait here
}
// Read the data without locks.

如果作者已经完成填充地图,上面的代码可以避免对读者进行任何锁定,如果他们没有,那么你可以采用典型的pthread_cond / mutex技术。 上面的代码是正确的,当且仅当你有作家那么读者(但那就是你所说的),否则它将会失败。