本文地址:(LYanger的博客:http://blog.youkuaiyun.com/freeelinux/article/details/53695111)
本文之前,我只是对自旋锁有所了解,知道它是做什么的,但是没有去测试实现过,甚至以为自旋锁只有kernel用这个,今天才发现POSIX有提供自旋锁的接口。下面我会分析一下自旋锁,并代码实现自旋锁和互斥锁的性能对比,以及利用C++11实现自旋锁。
一:自旋锁(spin lock)
The pthread_spin_lock() function shall lock the spin lock referenced by lock. The calling thread shall acquire the lock if
it is not held by another thread. Otherwise, the thread shall spin (that is, shall not return from the pthread_spin_lock()
call) until the lock becomes available. The results are undefined if the calling thread holds the lock at the time the
call is made. The pthread_spin_trylock() function shall lock the spin lock referenced by lock if it is not held by any
thread. Otherwise, the function shall fail.
The results are undefined if any of these functions is called with an uninitialized spin lock.
- 由于自旋时不释放CPU,因而持有自旋锁的线程应该尽快释放自旋锁,否则等待该自旋锁的线程会一直在哪里自旋,这就会浪费CPU时间。
- 持有自旋锁的线程在sleep之前应该释放自旋锁以便其他咸亨可以获得该自旋锁。内核编程中,如果持有自旋锁的代码sleep了就可能导致整个系统挂起。(下面会解释)
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
implementation shall permit the spin lock to be operated upon by any thread that has access to the memory where the spin
lock is allocated, even if it is allocated in memory that is shared by multiple processes.
If the Thread Process-Shared Synchronization option is supported and the value of pshared is PTHREAD_PROCESS_PRIVATE, or if
the option is not supported, the spin lock shall only be operated upon by threads created within the same process as the
thread that initialized the spin lock. If threads of differing processes attempt to operate on such a spin lock, the behav‐
ior is undefined.
int pthread_spin_destroy(pthread_spinlock_t *lock);
lock. The effect of subsequent use of the lock is undefined until the lock is reinitialized by another call to
pthread_spin_init(). The results are undefined if pthread_spin_destroy() is called when a thread holds the lock, or if this
function is called with an uninitialized thread spin lock.
lock(), or pthread_spin_unlock() is undefined.
int pthread_spin_lock(pthread_spinlock_t *lock);
加锁函数,功能上文都说过了,不过这么一点值得注意:
These functions shall not return an error code of [EINTR].
int pthread_spin_trylock(pthread_spinlock_t *lock);
int pthread_spin_unlock(pthread_spinlock_t *lock);解锁函数。不是持有锁的线程调用或者解锁一个没有lock的spin lock这样的行为都是undefined的。
二:自旋锁和互斥锁的区别
如果大家去查阅Linux glibc中对pthreads API的实现NPTL(Native POSIX Thread Library) 的源码的话(使用”getconf GNU_LIBPTHREAD_VERSION”命令可以得到我们系统中NPTL的版本号),就会发现pthread_mutex_lock()操作如果 没有锁成功的话就会调用system_wait()的系统调用并将当前线程加入该mutex的等待队列里。而spin lock则可以理解为在一个while(1)循环中用内嵌的汇编代码实现的锁操作(印象中看过一篇论文介绍说在linux内核中spin lock操作只需要两条CPU指令,解锁操作只用一条指令就可以完成)。有兴趣的朋友可以参考另一个名为sanos的微内核中pthreds API的实现:mutex.c spinlock.c,尽管与NPTL中的代码实现不尽相同,但是因为它的实现非常简单易懂,对我们理解spin lock和mutex的特性还是很有帮助的。
对于自旋锁来说,它只需要消耗很少的资源来建立锁;随后当线程被阻塞时,它就会一直重复检查看锁是否可用了,也就是说当自旋锁处于等待状态时它会一直消耗CPU时间。
对于互斥锁来说,与自旋锁相比它需要消耗大量的系统资源来建立锁;随后当线程被阻塞时,线程的调度状态被修改,并且线程被加入等待线程队列;最后当锁可用 时,在获取锁之前,线程会被从等待队列取出并更改其调度状态;但是在线程被阻塞期间,它不消耗CPU资源。
因此自旋锁和互斥锁适用于不同的场景。自旋锁适用于那些仅需要阻塞很短时间的场景,而互斥锁适用于那些可能会阻塞很长时间的场景。
四:自旋锁与linux内核进程调度关系
spin_lock(&mr_lock):
//critical region
spin_unlock(&mr_lock);
#define __LOCK(lock) \
do { preempt_disable(); __acquire(lock); (void)(lock); } while (0)
- 单CPU非抢占内核下:自旋锁会在编译时被忽略(因为单CPU且非抢占模式情况下,不可能发生进程切换,时钟只有一个进程处于临界区(自旋锁实际没什么用了)
- 单CPU抢占内核下:自选锁仅仅当作一个设置抢占的开关(因为单CPU不可能有并发访问临界区的情况,禁止抢占就可以保证临街区唯一被拥有)
- 多CPU下:此时才能完全发挥自旋锁的作用,自旋锁在内核中主要用来防止多处理器中并发访问临界区,防止内核抢占造成的竞争。
五:linux发生抢占的时间
- 从系统调用返回用户空间
- 从中断处理程序返回用户空间
- 当从中断处理程序返回内核空间的时候,且当时内核具有可抢占性
- 当内核代码再一次具有可抢占性的时候(如:spin_unlock时)
- 如果内核中的任务显示的调用schedule() (这个我暂时不太懂)
六:spin_lock和mutex实际效率对比
1.++i是否需要加锁?
2.spin_lock代码
#include <iostream>
#include <thread>
#include <pthread.h>
#include <sys/time.h>
#include <unistd.h>
int num = 0;
pthread_spinlock_t spin_lock;
int64_t get_current_timestamp()
{
struct timeval now = {0, 0};
gettimeofday(&now, NULL);
return now.tv_sec * 1000 * 1000 + now.tv_usec;
}
void thread_proc()
{
for(int i=0; i<100000000; ++i){
pthread_spin_lock(&spin_lock);
++num;
pthread_spin_unlock(&spin_lock);
}
}
int main()
{
pthread_spin_init(&spin_lock, PTHREAD_PROCESS_PRIVATE);//maybe PHREAD_PROCESS_PRIVATE or PTHREAD_PROCESS_SHARED
int64_t start = get_current_timestamp();
std::thread t1(thread_proc), t2(thread_proc);
t1.join();
t2.join();
std::cout<<"num:"<<num<<std::endl;
int64_t end = get_current_timestamp();
std::cout<<"cost:"<<end-start<<std::endl;
pthread_spin_destroy(&spin_lock);
return 0;
}
3.mutex代码
#include <iostream>
#include <thread>
#include <pthread.h>
#include <sys/time.h>
#include <unistd.h>
int num = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int64_t get_current_timestamp()
{
struct timeval now = {0, 0};
gettimeofday(&now, NULL);
return now.tv_sec * 1000 * 1000 + now.tv_usec;
}
void thread_proc()
{
for(int i=0; i<1000000; ++i){
pthread_mutex_lock(&mutex);
++num;
pthread_mutex_unlock(&mutex);
}
}
int main()
{
int64_t start = get_current_timestamp();
std::thread t1(thread_proc), t2(thread_proc);
t1.join();
t2.join();
std::cout<<"num:"<<num<<std::endl;
int64_t end = get_current_timestamp();
std::cout<<"cost:"<<end-start<<std::endl;
pthread_mutex_destroy(&mutex); //maybe you always foget this
return 0;
}
4.结果分析
for(int i=0; i<1000000; ++i){
pthread_spin_lock(&spin_lock);
++num;
for(int i=0; i<100; ++i){
//do nothing
}
pthread_spin_unlock(&spin_lock);
}
七:使用C++实现自主实现自旋锁
#pragma once
#include <atomic>
class spin_lock {
private:
std::atomic<bool> flag = ATOMIC_VAR_INIT(false);
public:
spin_lock() = default;
spin_lock(const spin_lock&) = delete;
spin_lock& operator=(const spin_lock) = delete;
void lock(){ //acquire spin lock
bool expected = false;
while(!flag.compare_exchange_strong(expected, true));
expected = false;
}
void unlock(){ //release spin lock
flag.store(false);
}
};
测试文件,仅给出关键部分:
int num = 0;
spin_lock sm;
void thread_proc()
{
for(int i=0; i<10000000; ++i){
sm.lock();
++num;
sm.unlock();
}
}
317



被折叠的 条评论
为什么被折叠?



