pthread互斥量的开销?

我正在尝试使C ++ API(用于Linux和Solaris)是线程安全的,以便可以从不同线程调用其函数,而不会破坏内部数据结构。 在我目前的方法中,我使用pthread mutexes来保护对成员variables的所有访问。 这意味着一个简单的getter函数现在可以locking和解锁一个互斥锁,而且我担心这个开销,尤其是当API主要用于单线程应用程序时,任何互斥锁似乎都是纯粹的开销。

所以,我想问一下:

  • 你有使用锁的单线程应用程序的性能有什么经验吗?
  • 这些locking/解锁呼叫有多昂贵,比如。 一个简单的“返回this-> isActive”访问一个布尔成员variables?
  • 你知道更好的方法来保护这种variables访问?

Solutions Collecting From Web of "pthread互斥量的开销?"

所有的现代线程实现都可以完全在用户空间中处理一个无争用的互斥锁(只需要几个机器指令) – 只有当存在争用时,库才能调用内核。

还有一点要考虑的是,如果一个应用程序没有明确地链接到pthread库(因为它是一个单线程应用程序),它只会得到虚拟pthread函数(根本不会做任何锁定)应用程序是多线程的(并链接到pthread库),将使用完整的pthread函数。

最后,正如其他人已经指出的那样,保护类似于互斥体的isActive的getter方法没有意义 – 一旦调用者有机会查看返回值,值可能已经被改变(如互斥体只锁定在getter方法内)。

“一个互斥体需要一个OS上下文切换,这相当昂贵。”

  • 在Linux上,这是不正确的,互斥体是通过使用futex'es来实现的。 正如cmeerw指出的那样,获得一个无争议的(即不是已经被锁定的)互斥体是一些简单指令的问题,并且通常在25ns的电流硬件的范围内。

更多信息: 互斥

大家应该知道的数字

这有点偏离主题,但你似乎是线程的新手 – 一方面,只锁定线程可以重叠的地方。 然后,尽量减少这些地方。 另外,不要试图锁定每一个方法,而应该考虑线程正在做什么(总体)与一个对象,并作出一个调用,并锁定。 尽量让你的锁尽可能高(这又提高了效率,可能/帮助/避免死锁)。 但是锁不能“组成”,你必须在思维上至少在线程的位置和重叠处交叉组织你的代码。

我做了一个类似的库,锁定性能没有任何问题。 (我不能确切地告诉你它们是如何实现的,所以我不能确定地说这不是什么大问题。)

我会先去弄正确的(即使用锁),然后担心性能。 我不知道更好的办法; 这就是互斥体的构建。

单线程客户端的替代方案是使用预处理器来构建库的非锁定和锁定版本。 例如:

#ifdef BUILD_SINGLE_THREAD inline void lock () {} inline void unlock () {} #else inline void lock () { doSomethingReal(); } inline void unlock () { doSomethingElseReal(); } #endif 

当然,这样会增加一个额外的构建来维护,就像分发单线程和多线程版本一样。

我可以从Windows告诉你,互斥量是一个内核对象,因此会产生(相对)显着的锁定开销。 为了获得更好的表现锁,当你需要的是一个线程工作的时候,就是使用关键部分。 这不会跨进程工作,只是单个进程中的线程。

然而.. Linux是多进程锁定的不同的野兽。 我知道一个互斥体是使用原子CPU指令实现的,只适用于一个进程 – 所以它们将具有与win32临界区相同的性能 – 即非常快。

当然,最快的锁定是完全没有的,或者尽可能少地使用它们(但是如果你的lib是在一个高度线程化的环境中使用的,你会想尽可能短地锁定:锁定,做某事,解锁,做别的事情,然后再次锁定比在整个任务中保持锁定更好 – 锁定的成本不是锁定的时间,而是一个线程绕着拇指等待的时间为另一个线程释放一个它想要的锁!)

互斥体需要OS上下文切换。 这相当昂贵。 CPU仍然可以每秒钟执行数十万次,而不会有太多的麻烦,但是比没有互斥体的代价要昂贵得多。 把它放在每个变量访问上可能是矫枉过正的。

这也可能不是你想要的。 这种蛮力锁定往往会导致死锁。

你知道更好的方法来保护这种变量访问?

设计您的应用程序,尽可能少的数据共享。 代码的某些部分应该是同步的,可能是一个互斥体,但只有那些实际上是必需的。 通常不是单独的变量访问,而是包含必须以原子方式执行的变量访问组的任务。 (也许你需要设置你的is_active标志以及一些其他的修改,设置这个标志是否有意义并且不对对象作进一步的修改?)

我很好奇使用pthred_mutex_lock/unlock的开销。 我有一个场景,我需要从1500-65K字节的任何地方复制,而不使用互斥锁或使用互斥锁,并执行一个指向所需数据的指针。

我写了一个短循环来测试每个

 gettimeofday(&starttime, NULL) COPY DATA gettimeofday(&endtime, NULL) timersub(&endtime, &starttime, &timediff) print out timediff data 

要么

 ettimeofday(&starttime, NULL) pthread_mutex_lock(&mutex); gettimeofday(&endtime, NULL) pthread_mutex_unlock(&mutex); timersub(&endtime, &starttime, &timediff) print out timediff data 

如果我的拷贝数少于4000字节,那么直接复制操作花费的时间就少了。 但是,如果我复制超过4000个字节,那么执行互斥锁/解锁的成本就会更低。

互斥锁定/解锁的时间在3到5之间,包括用于当前时间的gettimeofday的时间,大约需要2次使用

对于成员变量访问,您应该使用读/写锁,这些锁的开销稍微小一些,并允许多个并发读取而不会阻塞。

在很多情况下,如果你的编译器提供了它们(如果你使用gcc或者icc __sync_fetch *()等等),你可以使用原子构建),但是它们不是很难正确处理。

如果可以保证访问是原子的(例如在x86上,双字读或写总是原子的,如果是对齐的,而不是读 – 修改 – 写),则通常可以避免使用锁而使用volatile。这是不可移植的,需要硬件的知识。

那么一个次优而简单的方法就是在你的互斥锁周围放置宏并解锁。 然后有一个编译器/ makefile选项来启用/禁用线程。

防爆。

 #ifdef THREAD_ENABLED #define pthread_mutex_lock(x) ... //actual mutex call #endif #ifndef THREAD_ENABLED #define pthread_mutex_lock(x) ... //do nothing #endif 

然后在编译时做一个gcc -DTHREAD_ENABLED来启用线程。

再次,我不会在任何大型项目中使用这种方法。 但只有当你想要的东西相当简单。