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. Linux,IDS入侵防御系统

    https://www.comparitech.com/net-admin/network-intrusion-detection-tools/11 2018年的顶级入侵检测工具 https://op ...

  2. Python基础( )

    一. 文件修改 f = open("yesterday.txt",'r') f1 = open("yesterday2.txt",'w') for line i ...

  3. Java 枚举(enum) 详解4种常见的用法

    JDK1.5引入了新的类型——枚举.在 Java 中它虽然算个“小”功能,却给我的开发带来了“大”方便. 大师兄我又加上自己的理解,来帮助各位理解一下. 用法一:常量 在JDK1.5 之前,我们定义常 ...

  4. day3 zookeeper

    PS:在生产的场景中,一般有两个需求:1.提供设备的注册 2.对所注册的接口进行监听.zookeeper就是提供这样的功能,它本身就是一个集群,如果存在半数以上的节点活着就能提供服务,本身就具备很高的 ...

  5. linqpad使用方法备忘

    1.使用EF更新数据库 void Main() { select item).ToList(); CM_BookPages.DeleteAllOnSubmit(items); SubmitChange ...

  6. Java基础三(Scanner键盘输入、Random随机数、流程控制语句)

    1.引用类型变量的创建及使用2.流程控制语句之选择语句3.流程控制语句之循环语句4.循环高级 ###01创建引用类型变量公式 * A: 创建引用类型变量公式 * a: 我们要学的Scanner类是属于 ...

  7. 使用 --image-repository 解决kubeadm 安装k8s 集群 谷歌镜像墙的问题

    从网上我们看到的好多kubeadm 安装k8s 的时候都说需要下拉取镜像,然后修改,实际上 我们可以使用配置参数,快速的跳过墙的问题 说明: 基础镜像,我们仍然存在,拉取的问题,但是dockerhub ...

  8. 01python简介

    目录 1.  Python起源 2.  解释器 3.  Python 的设计目标 4.  Python 的设计哲学 5.  为什么选择  Python ? 6.  Python 特点 7.  Pyth ...

  9. P2P Downloader

    P2P Downloader , 当然就是 P2P 下载器了 , 就是和 比特精灵 差不多的那种 .  ^ ^ 不过这个项目没有代码 , 懒得写代码了 , 就文字描述一下吧 .   ^ ^ P2P 下 ...

  10. eclipse卡死在search for main types 20 files to index

    run as application时,提示search for main types  20 files to index (*/*/*.jar)某个maven依赖jar出了问题,找不到main方法 ...