1. 引言 现代计算机,即使很小的智能机亦或者平板电脑,都是一个多核(多CPU)处理设备,如何充分利用多核CPU资源,以达到单机性能的极大化成为我们码农进行软件开发的痛点和难点。在多核服务器中,采用多进程或多线程来并行处理任务,俨然成为了大家性能调优的标准解决方案。多进程(多线程)的并行编程方式,必然要面对共享数据的访问问题,如何并发、高效、安全地访问共享数据资源,成为并行编程的一个重点和难点。 传统的共享数据访问方式是采用同步原语(临界区、锁、条件变量等)来达到共享数据的安全访问,然而,同步恰恰和
这是 RSC 关于 Go 内存模型系列文章的第二篇,介绍了 Java,C/C++,Rust,JavaScript 等高级语言的内存模型。对于高级语言来说,如何定义竞争,如何避免竞争,竞争发生时编程语言能提供什么保证都是内存模型需要考虑的问题。
之前对于C++的原子变量操作总是感到困惑,在读到关于Go 1.19更新内存模型背景的系列文章后有了一些新领悟。本文将从硬件出发进行介绍,然后看看一些「现代」编程语言规范中定义的内存模型,最后简单聊聊Go 1.19内存模型的更新。
原子操作 通常我们代码中的a = a + 1这样的一行语句,翻译成汇编后蕴含着3条指令: ldr x0, &a add x0,x0,#1 str x0,&a 即 (1)从内存中读取a变量到X0寄存器 (2)X0寄存器加1 (3)将X0写入到内存a中 既然是3条指令,那么就有可能并发,也就意味着返回的结果可能不是预期的。 然后在linux kernel的操作系统中,提供访问原子变量的函数,用来解决上述问题。其中部分原子操作的API如下: atomic_read atomic_add_return(i,v) a
这是Russ Cox的第二篇Programming Language Memory Models。
MIPS架构中,中断、异常、系统调用以及其它可以中断程序正常执行流的事件统称为异常(exception),统一由异常处理机制进行处理。
为了实现线程间同步,一般都要在执行关键代码段之前加互斥(Mutex)锁,且在执行完关键代码段之后解锁。为了实现所谓的互斥锁的概念,一般都需要所在平台提供支持。
读了第15章,大致感觉到了CAS的乐观锁特性。“锁”这个词太有意思了,你能体会到几个意思?
之前学习了一些并发原语,已经认为差不多可以应对很多场景了,但是为什么还要学习原子操作呢?原来,在一些场景中,使用并发原语可能更加复杂,为了更轻松地实现底层的优化。
因为现代操作系统是多处理器计算的架构,必然更容易遇到多个进程,多个线程访问共享数据的情况,如下图所示:
cmpxchg是X86比较交换指令,这个指令在各大底层系统实现的原子操作和各种同步原语中都有广泛的使用,比如linux内核,JVM,GCC编译器等,cmpxchg就是比较交换指令,了解cmpxchg之前先了解原子操作。
CAS 一般采用原子级的read-modify-write原语来实现Lock-Free算法,其中LL和SC是Lock-Free理论研究领域的理想原语,但实现这些原语需要CPU指令的支持,非常遗憾的是目前没有任何CPU直接实现了SC原语。根据此理论,业界在原子操作的基础上提出了著名的CAS(Compare-And-Swap)操作来实现Lock-Free算法,Intel实现了一条类似该操作的指令:cmpxchg8。 CAS原语负责将某处内存地址的值(1个字节)与一个期望值进行比较,如果相等,则将该内存地址处的值
本系列参考: 学习开发一个RISC-V上的操作系统 - 汪辰 - 2021春 整理而来,主要作为xv6操作系统学习的一个前置基础。
C++11其实主要就四方面内容,第一个是可变参数模板,第二个是右值引用,第三个是智能指针,第四个是内存模型(Memory Model)。
这是 RSC 关于 Go 内存模型系列文章的最后一篇,介绍了 Go 处理竞争的整体思路和后续需要或可能做的一些更新,主要包括需要在文档中明确清楚 Go 能保证什么,不能保证什么以及一些可能需要添加的 API。作者更多的是站在 Go 明了易用的设计哲学角度去思考每种方案的优缺点。
单实例模式(singleton)下要求一个类只能有一个实例,如何保证只创建一个实例?类的静态成员延迟初始化要求静态成员只能被初始化一次,也有类似的问题。 在单线程环境下,这事儿很好办。
👋 你好,我是 Lorin 洛林,一位 Java 后端技术开发者!座右铭:Technology has the power to make the world a better place.
时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 262144K,其他语言524288K 64bit IO Format: %lld 题目描述 牛牛有一颗大小为n的神奇Link-Cut 数组,数组上的每一个节点都有两种状态,一种为link状态,另一种为cut状态。数组上任意一对处于link状态的无序点对(即(u,v)和(v,u)被认为是同一对)会产生dis(u,v)的link能量,dis(u,v)为数组上u到v的距离。
然后看看标准C++基金会(https://isocpp.org)怎么说的(官方链接):
给定 a 、b 、c 、d 、x 、y ,求 \prod \limits^{b} _ {i=a}\prod \limits^{d} _ {j=c}gcd(x^i,y^j) 。
CPU都有自己的L1、L2、L3缓存,CPU会将常用的数据,从主内存同步到缓存中,以此来提高数据的访问速度。如果CPU修改了缓存中的数据,就会从缓存更新到主内存中。
以下ConcurrentHashMap以jdk8中为例进行分析,ConcurrentHashMap是一个线程安全、基于数组+链表(或者红黑树)的kv容器,主要特性如下:
编程语言的内存模型是理解编程语言如何管理和操作计算机内存的关键。 它定义了编程语言中变量、数据结构和程序的存储方式,以及它们之间的交互方式。通过理解内存模型,程序员可以更有效地利用内存资源,优化程序性能,并避免常见的内存错误。
明确项目目标,是指我们希望程序达成什么目的,实现什么功能,从而帮我们将项目拆解成不同的单元;而一个妥当的拆解方案,难度适度递增,能帮我们逐步顺利执行,最终完成项目。这三个步骤可以说是环环相扣的(同时在这个过程中,我们要思考所需要的知识,以及如何去索取新的知识,找到切入点)。下面开始今天的主题解析:
活锁、死锁本质上是一样的,原因是在获取临界区资源时,并发多个进程/线程声明资源占用(加锁)的顺序不一致,死锁是加不上就死等,活锁是加不上就放开已获得的资源重试,其实单机场景活锁不太常见。举个例子资源A和B,进程P1和P2,
ConcurrentHashMap (以下简称C13Map) 是并发编程出场率最高的数据结构之一,大量的并发CASE背后都有C13Map的支持,同时也是JUC包中代码量最大的组件(6000多行),自JDK8开始Oracle对其进行了大量优化工作。
在《C++ 并发编程》一文中,我们已经介绍了C++11到C++17在并发编程方面的新增API。
在阅读这篇博客之前,希望你对HashMap已经是有所理解的,如果你对java的cas操作也是有一定了解的,因为在这个类中大量使用到了cas相关的操作来保证线程安全的。
2021 年 2 月 11 号,Rust 1.50 稳定版发布[1]。1.50版更新包括:
ConcurrentHashMap 是在 HashMap 的线程安全的版本,不允许 空键空值。
ConcurrentHashMap是conccurrent家族中的一个类,由于它可以高效地支持并发操作,以及被广泛使用,经典的开源框架Spring的底层数据结构就是使用ConcurrentHashMap实现的。与同是线程安全的老大哥HashTable相比,它已经更胜一筹,因此它的锁更加细化,而不是像HashTable一样为几乎每个方法都添加了synchronized锁,这样的锁无疑会影响到性能。
摘要:长期以来,大多数分立加速器都使用各代 PCI-Express 接口连接到主机系统。然而,由于缺乏对加速器和主机缓存之间一致性的支持,细粒度的交互需要频繁的缓存刷新,甚至需要使用低效的非缓存内存区域。加速器缓存一致性互连 (CCIX) 是第一个支持缓存一致性主机加速器附件的多供应商标准,并且已经表明了即将推出的标准的能力,例如 Compute Express Link (CXL)。在我们的工作中,当基于 ARM 的主机与两代支持 CCIX 的 FPGA 连接时,我们比较了 CCIX 与 PCIe 的使用情况。我们为访问和地址转换提供低级吞吐量和延迟测量,并检查使用 CCIX 在 FPGA 加速数据库系统中进行细粒度同步的应用级用例。我们可以证明,从 FPGA 到主机的特别小的读取可以从 CCIX 中受益,因为其延迟比 PCIe 短约 33%。不过,对主机的小写入延迟大约比 PCIe 高 32%,因为它们携带更高的一致性开销。对于数据库用例,即使在主机-FPGA 并行度很高的情况下,使用 CCIX 也可以保持恒定的同步延迟。
在多线程编程中,确保线程之间的可见性和数据一致性是非常重要的。Java中提供了volatile关键字和原子操作机制,用于解决这些问题。本文将深入讨论volatile关键字和原子操作的用法,以及它们在多线程编程中的重要性和注意事项。
原子操作(atomic operation)指的是由多步操作组成的一个操作。如果该操作不能原子地执行,则要么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤的一个子集。
这个系列的文章里介绍了很多并发编程里经常用到的技术,除了Context、计时器、互斥锁还有通道外还有一种技术--原子操作在一些同步算法中会被用到。今天的文章里我们会简单了解一下Go语言里对原子操作的支持,然后探讨一下原子操作和互斥锁的区别。
Golang的atomic包提供了一组原子操作函数,用于在多个goroutine之间安全地访问和修改共享变量。这些原子操作函数可以保证对共享变量的操作原子性的,从而避免了竞态条件的发生。本文将深入探讨Golang的atomic包的原子操作。
ConcurrentHashMap这个类在java.lang.current包中,这个包中的类都是线程安全的。ConcurrentHashMap底层存储数据的结构与1.8的HashMap是一样的,都是数组+链表(或红黑树)的结构。在日常的开发中,我们最长用到的键值对存储结构的是HashMap,但是我们知道,这个类是非线程安全的,在高并发的场景下,在进行put操作的时候有可能进入死循环从而使服务器的cpu使用率达到100%;sun公司因此也给出了与之对应的线程安全的类。在jdk1.5以前,使用的是HashTable,这个类为了保证线程安全,在每个类中都添加了synchronized关键字,而想而知在高并发的情景下相率是非常低下的。为了解决HashTable效率低下的问题,官网在jdk1.5后推出了ConcurrentHashMap来替代饱受诟病的HashTable。jdk1.5后ConcurrentHashMap使用了分段锁的技术。在整个数组中被分为多个segment,每次get,put,remove操作时就锁住目标元素所在的segment中,因此segment与segment之前是可以并发操作的,上述就是jdk1.5后实现线程安全的大致思想。但是,从描述中可以看出一个问题,就是如果出现比较机端的情况,所有的数据都集中在一个segment中的话,在并发的情况下相当于锁住了全表,这种情况下其实是和HashTable的效率出不多的,但总体来说相较于HashTable,效率还是有了很大的提升。jdk1.8后,ConcurrentHashMap摒弃了segment的思想,转而使用cas+synchronized组合的方式来实现并发下的线程安全的,这种实现方式比1.5的效率又有了比较大的提升。
前面介绍了多线程间是通过互斥锁与条件变量来保证共享数据的同步的,互斥锁主要是针对过程加锁来实现对共享资源的排他性访问。很多时候,对共享资源的访问主要是对某一数据结构的读写操作,如果数据结构本身就带有排他性访问的特性,也就相当于该数据结构自带一个细粒度的锁,对该数据结构的并发访问就能更加简单高效,这就是C++11提供的原子数据类型< atomic >。下面解释两个概念:
在JVM中long和double型变量都是占用8个字节空间存储的, 而在读写时,是以4字节为单位操作的; 也就是要写入一个long型数据, 需要分别写入高位和低位, 共2次完成.
原子操作是指一个或者多个不可再分割的操作。这些操作的执行顺序不能被打乱,这些步骤也不可以被切割而只执行其中的一部分(不可中断性)。在 Java 中通过原子操作来完成工作内存和主内存的交互,其中原子操作又可分为如下几类:
互斥锁是一个很有用的同步工具,它可以保证每一时刻进入临界区的 goroutine 只有一个。读写锁对共享资源的写操作和读操作则区别看待,并消除了读操作之间的互斥。
底层原理疑问 CAS是比较并交换,AtomicInteger最终都是调用Unsafe.compareAndSwapInt方法进行实现,那Unsafe.compareAndSwapInt为什么是原子性的
前言 大数据浪潮下,海量数据处理能力的提升是推动大数据不断前行的基础,海量数据处理的分布式系统应运而生,hdfs、hadoop、spark、storm、MQ等等。分布式系统运行的核心是集群化部署,分散化管理,任务均摊,平衡化运行。节点异常、机器异常、运营操作、策略变更都会打破原有的平衡状态进入一种不平衡状态,平台通过状态管理和协议交互逐步演进到另一种平衡状态,同时要保证这种演进过程中系统计算正确性。打破原有的平衡状态的场景非常多,复杂的平衡演进过程中又有很多的场景可能出现,这种交织的变化对分布式系统测试,
An atomic function performs a read-modify-write atomic operation on one 32-bit or 64-bit word residing in global or shared memory. For example, atomicAdd() reads a word at some address in global or shared memory, adds a number to it, and writes the result back to the same address. The operation is atomic in the sense that it is guaranteed to be performed without interference from other threads. In other words, no other thread can access this address until the operation is complete. Atomic functions do not act as memory fences and do not imply synchronization or ordering constraints for memory operations (see Memory Fence Functions for more details on memory fences). Atomic functions can only be used in device functions.
原子操作可以保证正在进行的动作不被打断,即一旦开始,持续结束。对比互斥锁其优势在于,原子操作在C/C++的层面,是无锁操作,其既能解决并发问题又不会导致死锁。
当更新一个变量的时候,多出现数据争用的时候可能出现所意想不到的情况。这时的一般策略是使用synchronized解决,因为synchronized能够保证多个线程不会同时更新该变量。然而,从jdk 5之后,提供了粒度更细、量级更轻,并且在多核处理器具有高性能的原子操作类。因为原子操作类把竞争的范围缩小到单个变量上,这可以算是粒度最细的情况了。
我们看两个线程输出的count值都是0这显然是不正确的,原因就是因为++这个操作符不是一个原子操作。我们可以把这个操作符拆分开来看一下它的实现逻辑。
Java代码 编译之后 得到 Java字节码,被 类加载器加载到JVM中,最终 转化为汇编指令。
领取专属 10元无门槛券
手把手带您无忧上云