原理与底层实现 原子操作 C (原理与底层实践的关系)

本文目录导航:

C++原子操作 原理与底层实现

原子操作指的是由一系列步骤组成的一个操作,如果操作不能原子地执行,则要么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤的一个子集。

现代操作系统中常使用原子操作实现同步操作,这些操作被视为独立且不可分割。

在单核环境下,原子操作意味着线程在操作前不会被切换,或在操作完成之后切换。

更广义地,原子操作指一系列必须整体完成的操作步骤,如果任何一步操作未完成,所有已完成的步骤都必须回滚,确保操作要么未完成,要么全部完成。

在单核系统中,单个机器指令通常被视为原子操作(除编译器优化和乱序执行外)。

在多核系统中,原子操作可能因多指令流并行运行而变得复杂,特别是在操作同一块内存区域时,数据竞争问题可能产生。

解决这些问题通常通过内存栅障来实现,即在CPU执行原子操作期间,其他CPU必须停止对内存操作或不对指定内存进行操作。

在C++11之前,C++标准中未对原子操作进行规定。

而编译器如vs和gcc提供了原子操作API。

实现方式主要有两种:基于缓存加锁与总线加锁。

总线加锁是使用处理器的lock#信号,当一个处理器输出此信号时,其他处理器的请求被阻塞,该处理器可以独占共享内存。

但总线锁定将CPU与内存间的通信锁住,导致锁定期间其他处理器无法操作其他内存地址的数据,因此开销较大。

缓存加锁机制允许在同一时刻仅保证对某个内存地址的操作原子性。

在Pentium 6和当前处理器中,通过内存区域如果被缓存在处理器的缓存行中,并在Lock操作期间被锁定,可以实现复杂的原子性操作。

处理器内部的内存地址被修改,同时利用缓存一致性机制来保证操作的原子性,防止多个处理器同时修改同一缓存行的数据。

当其他处理器尝试回写已被锁定的缓存行数据时,会使得缓存行无效。

缓存锁定的使用存在限制,当操作的数据不能被缓存或跨多个缓存行时,处理器会调用总线锁定。

某些处理器不支持缓存锁定,即使锁定的内存区域在处理器缓存行中,也会使用总线锁定。

实现原子操作的指令包括位测试和修改、交换指令(如XADD、CMPXCHG)和其他一些操作数和逻辑指令(如ADD、OR),这些指令操作的内存区域会被加锁,阻止其他处理器同时访问。

编程中,Windows操作系统提供了Win32 API中的原子操作,主要包括加1减1操作、比较并交换操作以及原子写操作。

如InterlockedExchange函数用于将一个值赋给指定变量,并返回该变量未被赋值前的值。

在GCC编译器中,提供了原子操作API。

C++11中引入了atomic模板类,定义了用于原子操作的类模板,支持多种类型,如atomic_int、atomic_long。

C++11还定义了原子操作的具体函数,如比较并交换、原子写操作等。

使用C++进行服务器开发时,学习相关架构师课程和掌握C/C++、Linux、golang等技术对于提升能力至关重要。

LinuxC/C++服务器开发面试题、学习资料、教学视频和学习路线图(包括C/C++、Linux、golang等技术)可在特定学习交流群中免费获取,群号为。

原理与底层实现 原子操作 C (原理与底层实践的关系)

C++中,举个例子描述一下STL中的allocator分配器是个啥玩意儿?

说一说C++里的allocator。

我们知道,C++ STL里定义了很多的容器(containers),每一个容器的第二个模板参数都是allocator类型。

比方说在VC10里,vector类的模板声明为:template<class _Ty, class _Ax = allocator<_Ty> >class vector但是,基本上很少有人会自定义一个allocator。

一来,默认的allocator已经够用了;二来,确实不知道该怎么用。

一般来说,我们没有必要重新定义一个allocator。

自定义的方式主要是为了提高内存分配相关操作的性能。

而STL提供的方式性能已经足够好了。

事实上,在windows平台上,new的底层实现是基于C语言的malloc函数;malloc函数家族又是基于Windows HeapCreate、HeapAlloc、HeapFree等相关API来实现的(具体可以参考%VSInstallFolder%\VC\crt\src目录中的heapinit.c、malloc.c和等相关函数)。

先撇开性能的问题不说,我们看一看如何实现一个自己的allocator。

在C++ 2003标准文档里,关于allocator的说明其实并不多,大概就20.1.5 Allocator requirements和20.4.1 The default allocator两处主要位置。

虽然内容不多,但是足够我们写出一个自己的allocator。

根据Allocator requirements我们需要提供一些typedefs:1: template <typename T> 2: class CHxAllocator 3: { 4: public: 5: // typedefs... 6: typedef T value_type; 7: typedef value_type* pointer; 8: typedef value_type& reference; 9: typedef value_type const* const_pointer;10: typedef value_type const& const_reference;11: typedef size_tsize_type;12: typedef ptrdiff_t difference_type;13:14: // rebind...15: template <typename _other> struct rebind { typedef CHxAllocator<_other> other; };16: };在这里有一个比较不太容易理解的东西:rebind。

C++标准里这么描述rebind的:The member class template rebind in the table above is effectively a typedef template: if the name Allocator is bound to SomeAllocator<T>, thenAllocator::rebind<U>::other is the same type as SomeAllocator<U>.啥意思?可以用一个简单的例子来说明下:学校都学过数据结构,比方说栈、单向列表、树。

我们就拿栈和列表来对比,看看有什么大不一样的地方。

撇开数据结构上的差异,从allocator的角度来看,我们可以发现:堆栈是存贮元素本身的,但是列表实际上不是直接存储元素本身的。

要维护一个列表,我们至少还需要一个所谓的next的指针。

因此,虽然是一个保存int的列表list<int>,但是列表存储的对象并不是int本身,而是一个数据结构,它保存了int并且还包含指向前后元素的指针。

那么,list<int, allocator<int>>如何知道分配这个内部数据结构呢?毕竟allocator<int>只知道分配int类型的空间。

这就是rebind要解决的问题。

通过allocator<int>::rebind<_Node>()你就可以创建出用于分配_Node类型空间的分配器了。

接下来要提供其他的接口。

根据The default allocator的描述,我们要提供如下一些接口:pointer address(reference val) constconst_pointer address(const_reference val) const返回val的地址pointer allocate(size_type cnt, CHxAllocator<void>::const_pointer pHint = 0)分配空间。

类似malloc。

pHint可以无视,主要是给类库使用,用于提高性能。

void deallocate(pointer p, size_type n)释放空间,类似free。

size_type max_size() const throw()可分配的最大数量。

void construct(pointer p, const_reference val)在地址p所指向的空间,使用val进行填充。

需要使用到palcement new,以便保证调用到构造函数。

void destroy(pointer p)析构p指向的内存块中内容。

一般通过显示调研析构函数来执行。

allocator() throw ()allocator(const_reference) throw ()template <typename _other> allocator(CHxAllocator <_other> const&) throw()~CHxAllocator() throw() 各种构造函数和析构函数如何实现上面这些函数,你只要照抄标准库中的实现就可以了。

如果你想要用c的malloc和free来实现,也可以这么写:1: pointer allocate(size_type cnt, CHxAllocator<void>::const_pointer pHint = 0) 2: { 3: UNREFERENCED_PARAMETER(pHint); 4: 5: if (cnt <= 0) 6: { 7: return 0 ; 8: } 9:10: void* pMem = nullptr ;11: if (max_size() < cnt || (pMem = malloc(cnt * sizeof(value_type))) == NULL)12: {13: throw std::bad_alloc(0);14: }15:16: return static_cast <pointer>(pMem);17: }18:19: void deallocate(pointer p, size_type)20: {21: free(p);22: }23:24: void construct(pointer p, const_reference val)25: {26: :: new ((void *)p) T(val);27: }28:29: void destroy(pointer p)30: {31: p->~T();32: }基本上,我们就简单实现了一个自己的allocator。

另外,除了这些最主要的接口函数,你还需要实现比较操作符==和!=,但是这些函根据标准文档,都直接返回true和false。

开头已经说了,重写allocator的主要目的是为了提高性。

那怎样才能提高性能呢?直接使用Windows的HeapXXXX堆内存API?其实,你自己用一下就会发现,性能提升并不明显。

因为通过new,再通过malloc,最后通过HeapAlloc不比直接调用HeapAlloc多几句话。

如何实现一个高性能的allocator,需要借助memory pool的想法。

另外,侯捷的stl源码剖析里分析了SGI STL利用类似想法实现的一个alloc。

聊聊C语言中的malloc申请内存的内部原理

深入解析 C 语言中 malloc 函数的内部工作原理在深入探讨 malloc 函数的内部工作原理之前,让我们先了解一下它在现代编程语言中的作用。

现代语言通常在应用层实现内存分配器,这些分配器模仿内核的 SLAB 分配器,预先从操作系统申请内存并构建内存池。

当我们需要内存时,分配器直接从内存池中提供,而当我们释放内存时,分配器负责管理内存,并通过策略决定是否将其回收给操作系统。

这种方法既能灵活管理不同大小的内存对象,又能避免频繁调用 mmap 系统调用带来的开销。

常见的内存分配器包括 glibc 的 ptmalloc、Google 的 tcmalloc 和 Facebook 的 jemalloc 等。

我们将以经典的 ptmalloc 分配器为例,详细介绍 malloc 函数的内部工作原理。

这里,我们将使用 glibc 源码,下载地址为/gnu/glibc/。

我们使用的是版本 2.12.1。

ptmalloc 分配器通过多个分配区 arena 进行内存管理。

arena 支持多个分配区,以降低多线程操作时的锁开销。

每个分配区包含一个锁,用于处理内存申请竞争。

arena 的数据结构定义在 struct malloc_state 中,包含锁、内存管理数据结构和 next 指针,用于链接所有分配区。

分配区中的基础内存分配单位是 malloc_chunk,简称 chunk。

它包含 header 和 body 部分。

每次申请内存时,分配器都会分配一个合适的 chunk 给用户,返回 body 部分的地址供用户使用。

释放内存时,chunk 不会被操作系统回收,而是由分配器管理,body 部分的 fd、bk 字段用于双向链表操作。

ptmalloc 通过 fastbins、smallbins、largebins 和 unsortedbins 管理空闲内存块。

fastbins 用于快速分配小内存,smallbins、largebins 和 unsortedbins 使用 bins 数组管理。

top chunk 用于在所有 bins 都无法满足需求时尝试分配。

在 malloc 的工作过程中,分配器根据内存大小在 fastbins、bins、smallbins、largebins 和 unsortedbins 中查找合适的 chunk。

释放内存时,将 chunk 放回相应的管理链表。

此外,分配器还会处理 chunk 的拆分和合并,以优化内存管理。

了解这些原理后,我们再来看看 malloc 函数在 glibc 中的实现。

在 public_mALLOc 函数中,主要处理分配区和锁操作以避免冲突。

核心逻辑在 _int_malloc 函数中,它包含了分配尝试和操作系统内存申请的处理。

在尝试过程中,可能会涉及到 chunk 的切分和合并,以减少内存碎片。

以上内容详细介绍了 C 语言中 malloc 函数的内部工作原理。

为了更深入地了解相关技术,我们还上线了一系列技术视频,内容涵盖硬件原理、内存管理、进程管理、文件系统、网络管理、容器底层原理、Golang 语言运行时、性能观测和性能优化等多个方面。

欢迎访问我们的开发内功修炼视频上线页面,了解更多详情,并关注公众号「开发内功修炼」以获取更多技术资讯。

你可能想看:
« 上一篇
下一篇 »

相关推荐

FreeBasic:强大免费开源的64位basic编译器及应用优势

2025年05月10日

82阅读

脊柱侧弯矫正方法大盘点:运动康复、支具矫正等多种方式

2025年05月09日

87阅读

如何在winForm标题栏去掉标题文字且任务栏图标正常显示

2025年05月09日

86阅读

十一黄金周促销广告词大全,国庆购物狂欢等你来

2025年05月09日

78阅读

关闭快速编辑模式及插入模式,设置控制台指定文字颜色举例

2025年05月09日

63阅读

黄金搭档低糖高钙饮品包装及成分功效全解析,适合人群要知晓

2025年05月09日

91阅读