pthread互斥量的开销?

时间:2009-08-14 12:38:02

标签: c++ linux performance pthreads mutex

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

所以,我想问:

  • 您是否对使用锁定的单线程应用程序的性能有任何经验?
  • 与例如相比,这些锁定/解锁呼叫的费用是多少。 bool成员变量的简单“返回this-> isActive”访问权限?
  • 你知道更好的方法来保护这种变量访问吗?

9 个答案:

答案 0 :(得分:35)

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

要考虑的另一点是,如果应用程序没有显式链接到pthread库(因为它是单线程应用程序),它只会获得虚拟pthread函数(根本不执行任何锁定) - 只有当应用程序是多线程的(并链接到pthread库)时,才会使用完整的pthread函数。

最后,正如其他人已经指出的那样,使用互斥锁保护isActive之类的getter方法没有意义 - 一旦调用者有机会查看返回值,该值可能已经被更改(因为互斥锁仅锁定在getter方法中。)

答案 1 :(得分:20)

“互斥锁需要操作系统上下文切换。这相当昂贵。”

  • 在Linux上并非如此,其中互斥体是使用futex'es实现的。正如cmeerw指出的那样,获取无争议(即,尚未锁定)的互斥锁是一些简单指令的问题,并且通常在25纳秒/当前硬件的范围内。

欲了解更多信息: Futex

Numbers everybody should know

答案 2 :(得分:7)

这有点偏离主题,但你似乎是线程的新手 - 一方面,只能锁定线程可以重叠的地方。然后,尽量减少这些地方。此外,不要试图锁定每个方法,而是考虑线程正在做什么(整体)与一个对象,并进行一次调用,并锁定它。尽量让你的锁尽可能高(这再次提高效率,可能/帮助/避免死锁)。但锁不会“组合”,你必须在心理上至少按线程和重叠的方式对代码进行交叉组织。

答案 3 :(得分:4)

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

我首先要把它弄好(即使用锁)然后担心性能。我不知道更好的方法;这就是构建互斥锁的原因。

单线程客户端的替代方法是使用预处理器来构建库的非锁定vs锁定版本。 E.g:

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

当然,这会增加额外的构建来维护,因为您要分发单线程和多线程版本。

答案 4 :(得分:3)

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

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

当然,最快的锁定根本就没有,或者尽可能少地使用它们(但如果你的lib要在高度线程的环境中使用,你会想要锁定一小段时间尽可能:锁定,做某事,解锁,做其他事情,然后再次锁定比在整个任务中持有锁更好 - 锁定的成本不是锁定所花费的时间,而是线程缠绕的时间它的拇指等待另一个线程释放它想要的锁!)

答案 5 :(得分:2)

互斥锁需要OS上下文切换。那是相当昂贵的。 CPU仍然可以每秒进行数十万次而不会有太多麻烦,但它比拥有互斥锁要昂贵得多。将它放在每个变量访问权限上可能有点过头了。

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

  

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

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

答案 6 :(得分:2)

我很好奇使用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个usec之间运行,包括时间  当前时间的gettimeofday大约需要2次使用

答案 7 :(得分:1)

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

在许多情况下,你可以使用原子内置,如果你的编译器提供它们(如果你使用gcc或icc __sync_fetch *()等),但是它们非常难以正确处理。

如果你能保证访问是原子的(例如在x86上,dword读或写总是原子的,如果它是对齐的,而不是读 - 修改 - 写),你通常可以避免使用锁并使用volatile相反,但这是不可移植的,需要了解硬件。

答案 8 :(得分:0)

一个次优但简单的方法是在您的互斥锁和解锁周围放置宏。然后有一个编译器/ 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以启用线程。

我再也不会在任何大型项目中使用此方法。但只有你想要一些相当简单的东西。

相关问题