c++:自旋锁或互斥比较(简单计算)
c++: spin lock or mutex comparison (simple calculations)
对于简单的任务,自旋锁应该比互斥锁有更好的性能。然而,在这个简单的测试中(8 个线程递增一个计数器),结果显示不同:
#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>
#include <vector>
using namespace std;
class SpinLock {
private:
atomic_flag lck = ATOMIC_FLAG_INIT;
public:
void lock() { while(lck.test_and_set(memory_order_acquire)) {} }
void unlock() { lck.clear(memory_order_release); }
};
int total = 0;
#ifdef SPINLOCK
SpinLock my_lock;
#else
mutex my_lock;
#endif
void foo(int n)
{
for(int i = 0; i < 10000000; ++i) {
#ifdef SPINLOCK
lock_guard<SpinLock> lck(my_lock);
#else
lock_guard<mutex> lck(my_lock);
#endif
++total;
}
}
int main()
{
vector<thread> v;
for(int i = 0; i < 8; ++i)
v.emplace_back(foo, i);
for(auto& t : v)
t.join();
cout << "total: " << total << endl;
return 0;
}
测试自旋锁:
$ g++ -DSPINLOCK -std=c++11 -Wall -pthread test.cc
$ time ./a.out
total: 80000000
real 0m18.206s
user 2m17.792s
sys 0m0.003s
测试互斥:
$ g++ -std=c++11 -Wall -pthread test.cc
$ time ./a.out
total: 80000000
real 0m9.483s
user 0m6.451s
sys 1m6.043s
结果显示互斥量几乎比自旋锁快两倍。自旋锁在 "user cpu" 中花费的时间最多,而互斥锁在 "sys cpu" 中花费的时间最多。互斥锁是如何实现的,我应该在像这样的简单计算中使用互斥锁而不是自旋锁吗?谁能解释一下结果?
g++ 是 4.8.2,OS 是 Red Hat Enterprise Linux 7.
谢谢。
一些注意事项:
time
实用程序输出中显示的时间是线程正在使用的 CPU 时间,而不是实际时间。自旋锁甚至在等待期间使用 cpu,而内核互斥锁将在等待时执行其他进程中的其他线程,而不是为那个 CPU 时间计费你的进程,除了用于实际进行调度的那个(你请参阅互斥情况下的 sys 行)。
出于与上述相同的原因,在自旋锁的情况下,您从进程开始到结束必须等待的总时间可能更快,但是您的 cpu可能有更高的使用率,这是您观察到的。
如果碰撞几率较低,线程可能是一个不错的选择,也就是说,线程花在同步负载上的时间与它可以异步执行的负载相比要小。如果您的所有负载都受到互斥锁的保护,那么使用线程只会产生开销 - 您可能应该将其序列化。
如果你的碰撞几率和碰撞等待时间都很短,自旋锁是个不错的选择。在您的情况下,您有 8 个线程冲突到同一资源,然后要求资源在您释放后立即可用。这意味着平均有 1 个线程工作和其他 7 个自旋锁,总 CPU 使用时间是单线程所需时间的 8 倍(如果你有一个 8 核机器并且没有其他负载)它)。在互斥锁的情况下,线程会在资源可用时暂停并唤醒,因此没有等待的开销,但是锁定互斥锁将需要一些开销,因为内核要跟踪哪些进程正在等待互斥锁,即使它不是太大,您也正在执行 1.6 亿次互斥锁操作,总计 sys
计费给您的进程的时间
对于简单的任务,自旋锁应该比互斥锁有更好的性能。然而,在这个简单的测试中(8 个线程递增一个计数器),结果显示不同:
#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>
#include <vector>
using namespace std;
class SpinLock {
private:
atomic_flag lck = ATOMIC_FLAG_INIT;
public:
void lock() { while(lck.test_and_set(memory_order_acquire)) {} }
void unlock() { lck.clear(memory_order_release); }
};
int total = 0;
#ifdef SPINLOCK
SpinLock my_lock;
#else
mutex my_lock;
#endif
void foo(int n)
{
for(int i = 0; i < 10000000; ++i) {
#ifdef SPINLOCK
lock_guard<SpinLock> lck(my_lock);
#else
lock_guard<mutex> lck(my_lock);
#endif
++total;
}
}
int main()
{
vector<thread> v;
for(int i = 0; i < 8; ++i)
v.emplace_back(foo, i);
for(auto& t : v)
t.join();
cout << "total: " << total << endl;
return 0;
}
测试自旋锁:
$ g++ -DSPINLOCK -std=c++11 -Wall -pthread test.cc
$ time ./a.out
total: 80000000
real 0m18.206s
user 2m17.792s
sys 0m0.003s
测试互斥:
$ g++ -std=c++11 -Wall -pthread test.cc
$ time ./a.out
total: 80000000
real 0m9.483s
user 0m6.451s
sys 1m6.043s
结果显示互斥量几乎比自旋锁快两倍。自旋锁在 "user cpu" 中花费的时间最多,而互斥锁在 "sys cpu" 中花费的时间最多。互斥锁是如何实现的,我应该在像这样的简单计算中使用互斥锁而不是自旋锁吗?谁能解释一下结果?
g++ 是 4.8.2,OS 是 Red Hat Enterprise Linux 7.
谢谢。
一些注意事项:
time
实用程序输出中显示的时间是线程正在使用的 CPU 时间,而不是实际时间。自旋锁甚至在等待期间使用 cpu,而内核互斥锁将在等待时执行其他进程中的其他线程,而不是为那个 CPU 时间计费你的进程,除了用于实际进行调度的那个(你请参阅互斥情况下的 sys 行)。出于与上述相同的原因,在自旋锁的情况下,您从进程开始到结束必须等待的总时间可能更快,但是您的 cpu可能有更高的使用率,这是您观察到的。
如果碰撞几率较低,线程可能是一个不错的选择,也就是说,线程花在同步负载上的时间与它可以异步执行的负载相比要小。如果您的所有负载都受到互斥锁的保护,那么使用线程只会产生开销 - 您可能应该将其序列化。
如果你的碰撞几率和碰撞等待时间都很短,自旋锁是个不错的选择。在您的情况下,您有 8 个线程冲突到同一资源,然后要求资源在您释放后立即可用。这意味着平均有 1 个线程工作和其他 7 个自旋锁,总 CPU 使用时间是单线程所需时间的 8 倍(如果你有一个 8 核机器并且没有其他负载)它)。在互斥锁的情况下,线程会在资源可用时暂停并唤醒,因此没有等待的开销,但是锁定互斥锁将需要一些开销,因为内核要跟踪哪些进程正在等待互斥锁,即使它不是太大,您也正在执行 1.6 亿次互斥锁操作,总计 sys
计费给您的进程的时间