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. C++学习(四)(C语言部分)之 二进制

    二进制学习时的笔记(其实也没什么用,留着给自己看的) 二进制简介只有 0 1 优点:1.二进制状态简单2.可靠性.稳定性高3.运算规则简单,简化设计4.通用性强 二进制计算正数二进制十进制转二进制(除 ...

  2. 集合set-深入学习

    集合set,无序,是不允许重复内容的,也就是不允许重复元素,如果有重复,会自动忽略,可接收可迭代类型 (一般用于需要判断和处理交集时候用到) 集合与字典的区别是,集合没有键只有值,字典是有键的字典是一 ...

  3. HashMap的四种遍历方法,及效率比较(简单明了)

    https://yq.aliyun.com/ziliao/210955 public static void main(String[] args) { HashMap<Integer, Str ...

  4. wekpack笔记

    1. webpack 是一个用来构建我们应用程序中的 JavaScript 模块的工具: 2. 可以从CLI 或 API来开始使用 webpack.这里只讲从CLI来使用它: 3. 安装,需要在nod ...

  5. 【BZOJ1492】【NOI2007】货币兑换

    我果然不会斜率优化 原题: 小Y最近在一家金券交易所工作.该金券交易所只发行交易两种金券:A纪念券(以下简称A券)和 B纪念券(以下 简称B券).每个持有金券的顾客都有一个自己的帐户.金券的数目可以是 ...

  6. 【BZOJ3527】【ZJOI2014】力

    "FFT还不是随手写?"我终于能说这样的话了இwஇ 原题: 给出n个数qi,给出Fj的定义如下: 令Ei=Fi/qi,求Ei.   FFT嘛,直接推公式 然后就变成俩卷积了,FFT ...

  7. 【BZOJ1067】【SCOI2007】降雨量

    新人求助,降雨量那题,本机AC提交AC 原题: 我们常常会说这样的话:“X年是自Y年以来降雨量最多的”.它的含义是X年的降雨量不超过Y年,且对于任意Y<Z<X,Z年的降雨量严格小于X年.例 ...

  8. imrersize函数

    imrersize函数: 用法:imresize(图像I,method,倍数) 'nearest'(默认值)最近邻插值'bilinear'双线性插值'bicubic'双三次插值 使用方法: clear ...

  9. ipfs webui 管理界面

    ipfs 内置了一个webui 默认的端口是5001 访问地址 http://ip:5001/webui 环境准备 docker-compose 文件   version: "3" ...

  10. loki grafana 团队开源的,类似Prometheus 的log 系统

    Prometheus 主要面向的是metrics,但是loki 是log,这样加上grafana 强大的可视化以及alert能力, 我们可以做好多事情,loki 的设计来源于Prometheus. 组 ...