在基于哈希表实现的Map中一个常用技巧就是将哈希桶的数量设置为2的n次方,也就是,此后通过取余操作定位key所在桶的位置可以转换成与运算。之所以将取余运算改成与运算,一方面这两者计算的结果是一样的,另外一方面是因为与运算具有更好的性能,因为与运算指令周期是小于取余运算的。Java中的 HashMap 和Go中 map 都使用到这个技巧。
基于哈希表实现的Map中的取余运算转换成与运算的技巧,用数学语言来表达:
对于正整数x, y,如果x为2的n次方,n为正整数,那么 表达式是成立的。
最近在部门中做了一次技术分享,现将分享内容总结成博文发布出来,内容有删改。
Golang以并发见长,支持成千上万个协程调度。Golang中协程称为Goroutine,它是Go runtime调度中的最小执行单元,Goroutine的创建、管理、调度运行的机制采用的GMP模型。本次分享介绍的就是Golang调度机制的GMP模型。
并行(Parallelism) 指的是一个CPU时间片内可以同时做多件事情。并行强调的是某一时间点内能够同时处理多件事情,并行需要多核CPU提供支持。并行是并发的子集。
并发(Concurrency) 指的是是一种同时处理许多事情的能力,并行强调是某一时间段内能够同时处理多件事情。
最近读了《Operating Systems: Three Easy Pieces》一书,全书主要围绕虚拟化、并发和持久化这三个主题展开,其中并发部分中介绍锁的章节,行文风趣幽默,写得非常精彩。文中介绍了多种实现锁的方案,以及各种锁的适用场景和优缺点。本文基于该书中锁章节,以一个gopher的角度去分享、拓展书中介绍的锁,并尽量使用Go实现书中介绍的几款自旋锁。
锁(lock)的目的是给临界区(Critical Section)加上一层保护,以保证临界区中代码能够像单条原子指令一样执行。临界区指的是一个访问共享资源的程序片段,比如对全局变量的访问、更新。在Linux系统中保护临界区的机制除了锁之外,还有信号量,屏障,RCU等手段。
锁本质是一个变量,我们通过lock()和unlock()这两个语义函数来操作锁变量。当线程准备进入临界区时候,会调用lock()尝试获取锁,当该锁状态是未上锁状态时候,线程会成功获取到锁,从而进入到临界区,如果此时其他线程尝试获取锁而进入临界区,会阻塞或者自旋。获取锁并进入临界区的线程称为锁的持有者,当锁持有者退出临界区时候,调用unlock()来释放锁,那么阻塞等待的其他线程继续开始竞争这个锁。下面是获取锁和释放锁的代码示例:
1 | lock_t mutex; |
本文是并发编程系列博文的开篇。笔者将打算写一系列并发编程相关的博文。下面是具体索引,方便大家查看:
并发编程系列:死锁
并发编程系列:无锁编程之栈
并发编程系列:无锁编程之队列
并发编程系列:无锁编程之ring buffer
并发编程系列:无锁编程之优先级队列
并发编程系列:缓存一致性协议、内存屏障
Go 提供一个名为C
的伪包(pseudo-package)来与C 语言交互,这种Go语言与C语言交互的机制叫做CGO。当 Go 代码中加入import C
语句来导入C
这个不存在的包时候,会启动CGO特性。此后在Go 代码中我们可以使用C.
前缀来引用C语言中的变量、类型,函数等。
我们可以给import C
语句添加注释,在注释中可以引入C的头文件,以及定义和声明函数和变量,此后我们可以在 Go 代码中引用这些函数和变量。这种注释称为 序言(preamble)。需要注意的是 序言和import C
语句之间不能有换行,序言中的静态变量是不能被Go代码引用的,而静态函数是可以的。
1 | package main |
原文:7 Code Patterns in Go I Can’t Live Without
代码模式使你的程序更可靠、更高效,并使你的工作和生活更轻松
我已经为开发EDR解决方案工作了7年。这意味着我必须编写具有弹性和高效性的长时间运行的系统软件。我在这项工作中大量使用 Go,我想分享一些最重要的代码模式,你可以依靠这些模式你的程序更加可靠(reliable)和高效(efficient)。
我们经常需要检查某些对象是否存在。例如,我们可能想检查之前是否访问过某个文件或者URL。在这些情况下,我们可以使用map[string]struct{}
。如下所示:
使用空结构 struct{}
意味着我们不希望Map的值占用任何空间。有些人会使用 map[string]bool
,但基准测试表明 map[string]struct{}
在内存和时间上都表现得更好。相关基准测试可以查看这里。
一日午饭后散步中,同事问了一道Go相关的测试题目,是他之前面试中面试官问的一个题目,他到现在还没有找到答案。这道测试题就是本篇博文的标题:Go语言中如何在零内存分配情况下反转字符串?
笔者前段时间负责所在广告部门的ssp系统核心的几个grpc服务由虚拟机部署迁移到k8s环境下的技术方案设计与实施。本篇博文就专门介绍下k8s环境的部署grpc几个方案。这里面不涉及具体实施细节。我们k8s环境是采用华为云的k8s集群服务,我们ssp系统都是go语言开发的,这里面的grpc专指grpc-go。
容器是微服务的基石,可以做到每个服务快速autoscale,但随之带来的是服务的消亡是任意不定的,服务如何能够被调用方找到的难题。为了解决这个问题,就需要系统支持服务的注册和服务的发现。对于grpc来说,就是服务提供者grpc server会部署到多个k8s的Pod上,Pod的创建和消亡是任意时刻,不可预测,那就需要有一套机制能够发现grpc server所有Pod的端点信息,保证调用方(grpc client)能够及时准确获取服务提供方信息。所以grpc部署在k8s的方案也必要解决服务的注册和服务的发现。
此外调用方(grpc client)会维持grpc长连接,以及grpc底层使用HTTP/2协议,负载均衡不同与http和tcp,这一点在设计方案时候,也需要特别关注。
K8s service是一个命名负载均衡器,它可以将流量代理到一个或多个Pod(这里面的service指的是ClusterIP
类型的service)。grpc-go可以通过拨号直连到service,让service进行服务发现和负载均衡处理。
k8s service直连方案部署和开发简单,Pod扩容和缩容都可以及时感知。但是由于service负载均衡工作在4层,无法识别7层的HTTP/2协议,会导致负载均衡不均匀的问题。
原文是 Exploring Prometheus Go client metrics,有删改。
在这篇文章中,我将探索下Prometheus Go 客户端指标,这些指标由client_go
通过promhttp.Handler()
暴露出来的。通过这些指标能帮助你更好的理解 Go 是如何工作的。
想对Prometheus了解更多吗?你可以去学习下Monitoring Systems and Services with Prometheus,这是一门很棒的课程,可以让你快速上手。
让我们从一个简单的程序开始,它注册prom handler
并且监听8080端口:
1 |
|
在探究“Go语言中调用time.Now()时有没有发生系统调用?”这个问题之前,我们先复习下什么是系统调用。
系统调用(system call)指的是运行在用户空间的程序向操作系统内核请求具有更高权限的服务。究竟是哪些服务呢?这些服务指的是由操作系统内核进行管理的服务,比如进程管理,存储,内存,网络等。以打开文件为例子,用户程序需要调用open
和read
这两个系统调用,在c语言中要么使用libc库实现(底层也是系统调用),要么直接使用系统调用实现。
Linux系统中为什么一定要经过系统调用才能访问特资源呢,难道就不能在用户空间完成调用访问功能吗?之所以这么设计是考虑到系统隔离性,提高系统安全性和容错性,避免恶意攻击。操作系统把CPU访问资源的安全级别分为4个级别,这些级别称为特权级别(privilege level),也称为CPU环(CPU Rings)。在任一时刻,CPU都是在一个特定的特权级下运行的,从而决定了什么可以做,什么不可以做。这些级别可以形象的考虑成一个个圆环,里面是最高特权的Ring0,向外依次是Ring1,Ring2,最后是最低特权的Ring3。当发生系统调用时候,应用程序将会从应用空间进入内核空间,此时特权级别会由Ring3提升到Ring0,应用程序代码也会跳到相关系统调用代码处执行。