Windows和POSIX中都提供了自旋锁,我们也可以通过C++11的atomic来实现自旋锁。那么两者性能上面是什么关系?先引入实现代码:

#ifndef __spinlock_h__
#define __spinlock_h__ #include <atomic> #ifdef _WIN32 #include <Windows.h> class spinlock_mutex
{
public:
static constexpr DWORD SPINLOCK_COUNT = -;
public:
// 在初始化时,会出现资源不足的问题,这里忽略这个问题
// 具体参考Critical Sections and Error Handling(Windows via C/C++)
spinlock_mutex()
{
InitializeCriticalSectionAndSpinCount(&m_cs, SPINLOCK_COUNT);
} ~spinlock_mutex()
{
DeleteCriticalSection(&m_cs);
} void lock()
{
EnterCriticalSection(&m_cs);
} bool try_lock()
{
return TryEnterCriticalSection(&m_cs) == TRUE;
} void unlock()
{
LeaveCriticalSection(&m_cs);
} private:
CRITICAL_SECTION m_cs;
}; #elif defined(_POSIX_C_SOURCE) #include <pthread.h> class spinlock_mutex
{
public:
// 这里不处理可能出现的调用错误
spinlock_mutex()
{
pthread_spin_init(&m_cs, PTHREAD_PROCESS_PRIVATE);
} ~spinlock_mutex()
{
pthread_spin_destroy(&m_cs);
} void lock()
{
pthread_spin_lock(&m_cs);
} bool try_lock()
{
return pthread_spin_trylock(&m_cs) == ;
} void unlock()
{
pthread_spin_unlock(&m_cs);
} private:
pthread_spinlock_t m_cs;
}; #else class spinlock_mutex
{
std::atomic_flag flag;
public:
spinlock_mutex() :
flag{ ATOMIC_FLAG_INIT }
{} void lock()
{
while (flag.test_and_set(std::memory_order_acquire));
} void unlock()
{
flag.clear(std::memory_order_release);
} bool try_lock()
{
return !flag.test_and_set(std::memory_order_acquire);
}
}; #endif #endif // __spinlock_h__

下面给出一个简单测试,两组线程,一组用来插入,另外一组用来取出。测试结果显示:

(1)无论是Windows,还是POSIX提供的C语言版本的自旋锁,都和C++11使用atomic构建的自旋锁效率相近。

(2)在插入线程数和取出线程数相同的情况下,线程数越多,效率越低。

下面是测试代码:

#include <memory>
#include <cassert> #include <iostream>
#include <vector>
#include <thread>
#include <future>
#include <random>
#include <chrono> #include "spinlock.h"
#include <forward_list> struct student_name
{
student_name(int age = )
: age(age), next(nullptr)
{ } int age; student_name* next;
}; spinlock_mutex g_mtx;
std::forward_list<int> g_students; std::atomic<int> g_inserts; // insert num (successful)
std::atomic<int> g_drops; // drop num (successful) std::atomic<int> g_printNum; // as same as g_drops std::atomic<long long> g_ageInSum; // age sum when producing student_name
std::atomic<long long> g_ageOutSum; // age sum when consuming student_name std::atomic<bool> goOn(true); constexpr int INSERT_THREAD_NUM = ;
constexpr int DROP_THREAD_NUM = ; constexpr int ONE_THREAD_PRODUCE_NUM = ; // when testing, no more than this number, you know 20,000,00 * 100 * 10 ~= MAX_INT if thread num <= 10 inline void printOne(student_name* t)
{
g_printNum.fetch_add(, std::memory_order_relaxed);
g_ageOutSum.fetch_add(t->age, std::memory_order_relaxed);
g_drops.fetch_add(, std::memory_order_relaxed);
delete t;
} void insert_students(int idNo)
{
std::default_random_engine dre(time(nullptr));
std::uniform_int_distribution<int> ageDi(, ); for (int i = ; i < ONE_THREAD_PRODUCE_NUM; ++i)
{
int newAge = ageDi(dre);
g_ageInSum.fetch_add(newAge, std::memory_order_relaxed); {
std::lock_guard<spinlock_mutex> lock(g_mtx);
g_students.push_front(newAge); } // use memory_order_relaxed avoiding affect folly memory order
g_inserts.fetch_add(, std::memory_order_relaxed);
}
} void drop_students(int idNo)
{
while (auto go = goOn.load(std::memory_order_consume))
{
{
std::forward_list<int> tmp;
{
std::lock_guard<spinlock_mutex> lock(g_mtx);
std::swap(g_students, tmp);
}
auto it = tmp.begin();
while (it != tmp.end())
{
g_printNum.fetch_add(, std::memory_order_relaxed);
g_ageOutSum.fetch_add(*it, std::memory_order_relaxed);
g_drops.fetch_add(, std::memory_order_relaxed);
++it;
}
}
}
} int main()
{
auto start = std::chrono::system_clock::now(); std::vector<std::future<void>> insert_threads;
std::vector<std::future<void>> drop_threads; for (auto i = ; i != INSERT_THREAD_NUM; ++i)
{
insert_threads.push_back(std::async(std::launch::async, insert_students, i));
} for (auto i = ; i != DROP_THREAD_NUM; ++i)
{
drop_threads.push_back(std::async(std::launch::async, drop_students, i)); } for (auto& thread : insert_threads)
{
thread.get();
} std::this_thread::sleep_for(std::chrono::milliseconds()); goOn.store(false, std::memory_order_release); for (auto& thread : drop_threads)
{
thread.get();
} {
std::forward_list<int> tmp;
{
std::lock_guard<spinlock_mutex> lock(g_mtx);
std::swap(g_students, tmp);
}
auto it = tmp.begin();
while (it != tmp.end())
{
g_printNum.fetch_add(, std::memory_order_relaxed);
g_ageOutSum.fetch_add(*it, std::memory_order_relaxed);
g_drops.fetch_add(, std::memory_order_relaxed);
++it;
}
} auto end = std::chrono::system_clock::now();
std::chrono::duration<double> diff = end - start;
std::cout << "Time to insert and drop is: " << diff.count() << " s\n"; std::cout << "insert count1: " << g_inserts.load() << std::endl;
std::cout << "drop count1: " << g_drops.load() << std::endl;
std::cout << "print num1: " << g_printNum.load() << std::endl; std::cout << "age in1: " << g_ageInSum.load() << std::endl;
std::cout << "age out1: " << g_ageOutSum.load() << std::endl; std::cout << std::endl;
}

关于自选锁,还有以下内容需要说明:

(1)应用层用spinlock的最大问题是不能跟kernel一样的关中断(cli/sti),假设并发稍微多点,线程1在lock之后unlock之前发生了时钟中断,
 * 一段时间后才会被切回来调用unlock,那么这段时间中另一个调用lock的线程不就得空跑while了?这才是最浪费cpu时间的地方。
 * 所以不能关中断就只能sleep了,怎么着都存在巨大的冲突代价。

(2)具体参考:https://www.zhihu.com/question/55764216

Windows和pthread中提供的自旋锁的更多相关文章

  1. pthread中互斥量,锁和条件变量

    互斥量 #include <pthread.h> pthread_mutex_t mutex=PTHREAD_MUTEX_INTIIALIZER; int pthread_mutex_in ...

  2. Linux中自旋锁

    传统的spinlock Linux的的内核最常见的锁是自旋锁.自旋锁最多只能被一个可执行线程持有.如果一个执行线程试图获得一个被已经持有(争用)的自旋锁,那么该线程就会一直进行忙循环-旋转-等待锁重新 ...

  3. Linux内核同步:自旋锁

    linux内核--自旋锁的理解 自旋锁:如果内核配置为SMP系统,自旋锁就按SMP系统上的要求来实现真正的自旋等待,但是对于UP系统,自旋锁仅做抢占和中断操作,没有实现真正的“自旋”.如果配置了CON ...

  4. Optimistic concurrency control 死锁 悲观锁 乐观锁 自旋锁

    Optimistic concurrency control https://en.wikipedia.org/wiki/Optimistic_concurrency_control Optimist ...

  5. spinlock自旋锁de使用

    Linux内核中最常见的锁是自旋锁.一个自旋锁就是一个互斥设备,它只能有两个值:"锁定"和"解锁".如果锁可用,则"锁定"位被设置,而代码继 ...

  6. Nginx学习之四-Nginx进程同步方式-自旋锁(spinlock)

    自旋锁简介 Nginx框架使用了三种消息传递方式:共享内存.套接字.信号. Nginx主要使用了三种同步方式:原子操作.信号量.文件锁. 基于原子操作,nginx实现了一个自旋锁.自旋锁是一种非睡眠锁 ...

  7. JAVA锁机制-可重入锁,可中断锁,公平锁,读写锁,自旋锁,

    如果需要查看具体的synchronized和lock的实现原理,请参考:解决多线程安全问题-无非两个方法synchronized和lock 具体原理(百度) 在并发编程中,经常遇到多个线程访问同一个 ...

  8. Linux 同步方法剖析--内核原子,自旋锁和相互排斥锁

    在学习 Linux® 的过程中,您或许接触过并发(concurrency).临界段(critical section)和锁定,可是怎样在内核中使用这些概念呢?本文讨论了 2.6 版内核中可用的锁定机制 ...

  9. linux 自旋锁和信号量【转】

    转自:http://blog.csdn.net/xu_guo/article/details/6072823 版权声明:本文为博主原创文章,未经博主允许不得转载. 自旋锁最多只能被一个可执行线程持有( ...

随机推荐

  1. javascript 日常

    $('#code').bind('keypress', function (e) { //绑定回车处理 ) { console.log($("#code")); } }); $.a ...

  2. PS学习之动态表情制作

    准备素材 1. 2. 3. 4. 最后效果图: 在PS中打开四个图片 另外新建一个文件 用魔棒工具抠图 点击白色位置 右键选择反向 右键人物 选择拷贝的图层 重复,将四个图片扣好 拖到新建的文件里 如 ...

  3. 【git】git使用

    1.创建github账户 网站:https://github.com/ 注册省略 2.ssk-key客户端配置 作用:不用每次push,clone代码不需要输入用户名+密码 生成ssh-key ssh ...

  4. 如何将centos7作为DNS服务器

    简单来说,dns服务器是起到缓存的作用.比如说我们第一次dig www.baidu.com的时候,dns服务器因为没有解析过百度地址,所以它需要向上一级dns服务器进行查询,然后查询结果会缓存在这台d ...

  5. php 页面调转导致session丢失解决方法

    例如在a页面设置了会话,然后打印会话值,可以成功打印,但是调转到b页面后,会话丢失了. 原因有不少,一个原因就是没有在页面开头加入session_start();当然你也可以直接配置php.ini文件 ...

  6. malloc,calloc,alloca和free函数

    void *malloc(size_t size)因为返回类型为空,所以可以赋值到任何类型指针,其分配的空间大小为size,返回新分配内存地址的起始处的指针,其所分配的内存未经初始化,若分配失败返回N ...

  7. java保留2位小数及BigDecimal使用

    java保留两位小数的方法 import java.math.BigDecimal; import java.text.DecimalFormat; import java.text.NumberFo ...

  8. Dynamic Signals and Slots

    Ref https://doc.qt.io/archives/qq/qq16-dynamicqobject.html Trolltech | Documentation | Qt Quarterly ...

  9. [转]Java NIO通俗易懂简明教程

    Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API.本系列教程将有助于你学习和理解Java NIO. Java NIO提供了与 ...

  10. git push文件到远程github或者gitlab

    Git global setup git config --global user.name "luozeng" git config --global user.email &q ...