Go语言内存模型

名词定义 执行体 - Go里的Goroutine或Java中的Thread 背景介绍 内存模型的目的是为了定义清楚变量的读写在不同执行体里的可见性。理解内存模型在并发编程中非常重要,因为代码的执行顺序和书写的逻辑顺序并不会完全一致,甚至在编译期间编译器也有可能重排代码以最优化CPU执行, 另外还因为有CPU缓存的存在,内存的数据不一定会及时更新,这样对内存中的同一个变量读和写也不一定和期望一样。 和Java的内存模型规范类似,Go语言也有一个内存模型,相对JMM来说,Go的内存模型比较简单,Go的并发模型是基于CSP(Communicating Sequential Process)的,不同的Goroutine通过一种叫Channel的数据结构来通信;Java的并发模型则基于多线程和共享内存,有较多的概念(violatie, lock, final, construct, thread, atomic等)和场景,当然java.util.concurrent并发工具包大大简化了Java并发编程。 Go内存模型规范了在什么条件下一个Goroutine对某个变量的修改一定对其它Goroutine可见。 Happens Before 在一个单独的Goroutine里,对变量的读写和代码的书写顺序一致。比如以下的代码: package main import ( "log" ) var a, b, c int func main() { a = 1 b = 2 c = a + 2 log.Println(a, b, c) } 尽管在编译期和执行期,编译器和CPU都有可能重排代码,比如,先执行b=2,再执行a=1,但c=a+2是保证在a=1后执行的。这样最后的执行结果一定是1 2 3,不会是1 2 2。但下面的代码则可能会输出0 0 0,1 2 2, 0 2 3 (b=2比a=1先执行), 1 2 3等各种可能。 package main import ( "log" ) var a, b, c int func main() { go func() { a = 1 b = 2 }() go func() { c = a + 2 }() log. [Read More]

使用Go语言在树莓派上编程

WiringPi是树莓派上比较好的一个开发库,是用C语言写的。使用cgo,我们可以在Go语言里方便的调用WiringPI的函数,于是我包装了一个WiringPi-Go,目前支持wiringPi的基本功能,硬件SPI协议驱动Nokia 5110屏幕,以及中断,未来还会增加PWM和I2C协议的支持。 下面是一个完整的使用例子,结合了之前的两个电路:链接1,链接2 通过push button可以切换液晶屏显示不同脚本的输出内容。 lcd_switch.go package main import ( . "github.com/hugozhu/rpi" "github.com/hugozhu/rpi/pcd8544" "log" "os/exec" "time" ) const ( DIN = PIN_MOSI SCLK = PIN_SCLK DC = PIN_GPIO_2 RST = PIN_GPIO_0 CS = PIN_CE0 PUSHBUTTON = PIN_GPIO_6 CONTRAST = 40 //may need tweak for each Nokia 5110 screen ) var screen_chan chan int var TOTAL_MODES = 3 func init() { WiringPiSetup() pcd8544.LCDInit(SCLK, DIN, DC, CS, RST, CONTRAST) screen_chan = make(chan int, 1) } func main() { //a goroutine to check button push event go func() { last_time := time. [Read More]

使用tsar记录和监控树莓派CPU温度

夏天到了,树莓派的CPU温度也开始节节攀升,虽然我们也可以用云服务cosm来监控,但每5分钟采样一次精度不够高,每分钟采样一次则上传次数又太多了点。最好的方法还是使用tsar这样的工具本地高频(如每1分钟)采样,然后再定时将5分钟的均值上传到cosm绘图。 Tsar是淘宝的一个用来收集服务器系统和应用信息的采集报告工具,如收集服务器的系统信息(cpu,mem等),以及应用数据(nginx、swift等),收集到的数据存储在服务器磁盘上,可以随时查询历史信息,也可以将数据发送到nagios报警。Tsar能够比较方便的增加模块,只需要按照tsar的要求编写数据的采集函数和展现函数,就可以把自定义的模块加入到tsar中。 更新 [2013-04-14] mod_rpi已经被合并到了主干代码:https://github.com/alibaba/tsar/blob/master/modules/mod_rpi.c 只需要增加文件:/etc/tsar/conf.d/rpi.conf,内容为以下即可开始使用mod_rpi模块: mod_rpi on ####add it to tsar default output output_stdio_mod mod_rpi mod_rpi模块开发方法 首先按照安装说明,见https://github.com/alibaba/tsar将tsar和tsardevel安装好。 首先运行下面的命令生成mod_rpi模块: hugo@raspberrypi2 ~/projects/tsardevel $ tsardevel rpi build:make install:make install uninstall:make uninstall hugo@raspberrypi2 ~/projects/tsardevel $ ls rpi Makefile mod_rpi.c mod_rpi.conf 然后修改mod_rpi.c,增加读取CPU温度的逻辑: /* * (C) 2010-2011 Alibaba Group Holding Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www. [Read More]

Raspberry Pi的GPIO中断编程

背景介绍 树莓派的GPIO引脚不仅可以输出高低电平,也可以当做输入端口(可以想象成键盘输入),当GPIO接入的是高电平,GPIO的值可以认为是1,如果是低电平则是0。如下图所示,可以使用一个Push Button开关按键来控制GPIO 25(BCM Numbering)的高低电平以达到控制的目的。 GPIO 25和VCC(3.3V)之间通过R1(10K欧姆)和R2(1K欧姆)上拉电阻相连,当按键未被按下时,GPIO 25上拉到VCC,程序可以读到1,当按键按下时,GPIO 25被下拉电阻R2拉到GND(0V),程序可以读到0。如果不加R1,而GPIO 25不小心被设置成输出低电平时,将直接和VCC相连而造成短路,这样可能会烧掉这个引脚,所以加上限流电阻R1后,即使发生这样的情况,也不会出现短路情况。 应用 如果我们需要根据GPIO 25的值来控制树莓派,比如按下按钮时希望点亮某个LED或在液晶上显示当前时间,就需要通过程序来获取状态的变化。 一种常见的做法是在循环里不断读取该引脚的状态,当发生对应的变化的时执行控制逻辑,但显而易见,这种做法很消耗CPU,如果在循环增加sleep(1000)这样的调用,又很容易错过按键变化。较好的做法则是通过中断来实现。 最新的树莓派Raspbian和Arch Linux内核都已经包含了GPIO的中断处理支持。但使用前需要将指定GPIO引脚输出,方法如下: 首先可以通过命令echo 25 > /sys/class/gpio/export导出GPIO 25端口,执行成功后在相应的目录下看到以下文件,得益于Linux下一切都是文件的设计理念,GPIO的状态可以通过value文件来获取,这样就可以利用Linux的poll/epoll来获取value文件的变化(这点和Linux高性能网络编程是类似的)。 root@raspberrypi2 ~/projects/interrupt_test # ls -l /sys/class/gpio/gpio25/ total 0 -rw-r--r-- 1 root root 4096 Apr 8 23:56 active_low -rw-r--r-- 1 root root 4096 Apr 8 22:29 direction -rw-r--r-- 1 root root 4096 Apr 8 22:29 edge drwxr-xr-x 2 root root 0 Apr 8 23:56 power lrwxrwxrwx 1 root root 0 Apr 8 23:56 subsystem -> . [Read More]

备份Raspberry Pi

树莓派的操作系统安装在SD卡,使用一段时间后还是很有必要备份一下,以防哪天SD卡就坏了。 备份的目的地最方便的还是使用网络存储,我使用的是西部数据的MyBooklive3T网络硬盘。挺不错的一个产品,功能基本满足我的需求。 准备好备份目标盘,将Nas的备份目录mount到树莓派: mkdir /mnt/backup mount -t cifs //mybooklive/Public/Backup /mnt/backup -o guest 完整备份 确定相应的SD卡设备ID root@raspberrypi2 ~/bin # fdisk -l Disk /dev/mmcblk0: 1973 MB, 1973420032 bytes, 3854336 sectors Units = sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disk identifier: 0x0004f23a Device Boot Start End Blocks Id System /dev/mmcblk0p1 * 2048 186367 92160 c W95 FAT32 (LBA) /dev/mmcblk0p2 186368 3667967 1740800 83 Linux Disk /dev/sda: 2107 MB, 2107637760 bytes, 4116480 sectors Units = sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes 执行备份 [Read More]

在Raspberry Pi上使用硬件SPI

什么是SPI SPI (Serial Peripheral Interface),是一种高速,全双工,同步的通信总线协议,基于SPI的设备需要4根线: SDO / MOSI - 主设备数据输出,从设备数据输入 SDI / MISO - 主设备数据输入,从设备数据输出 SCLK / CLK - 时钟信号,由主设备产生 CS / SS - 从设备使能信号,由主设备控制 通过CS,主设备可以控制和哪个从设备通信。 Bit Banging Bit-banging是一种用软件替代专职硬件的串行通信的技术。软件直接对微处理器的管脚的状态进行设置和采样,其功能涵盖诸如:时钟,电平,同步等所有参数。与此不同的是(传统的串行通信技术中),专职硬件诸如 modem、UART 或者 位移寄存器等一般是用来处理这些参数并且提供一个(缓存)的数据接口,软件在这种情况下同信号处理无关。 bit-banging 具有明显优点诸如:让相同的设备运行不同的协议而只需很小的(甚至不需)硬件的改动。借助很少的额外设备,我们也许可以从数字管脚(数字终端)可以得到视频信号。 bit-banging 也有一些明显的缺点。在软件仿真的过程中消耗的能量比同样功能的专职硬件大。微处理器过忙地从管脚采样和发送采样信号到管脚。在同等微处理器处理能力下,系统常常会有些噪音。 在Rasperry Pi上使用Bit Banging在实际情况下有可能因为操作系统调度造成时钟信号不稳定而使设备收到错误的消息,具体的表现就是Nokia 5110屏在长时间运行过程中出现白屏或花屏现象,如下图: 采用硬件SPI,由Pi的管脚14号Pin(左边倒数第二个)SCLK发出一定频率的时钟信号。经过测试,这种方法产生的时钟信号比Bit Banging软件模拟产生的信号要稳定很多。 软件模拟时钟信号波形 硬件SPI时钟信号波形 测试Pi的硬件SPI 确认内核支持 root@raspberrypi2 ~/projects/spi_test # ls -la /dev/spi* crw------- 1 root root 153, 0 Jan 1 1970 /dev/spidev0.0 crw------- 1 root root 153, 1 Jan 1 1970 /dev/spidev0. [Read More]

在Raspberry Pi上使用Google Channel服务搭建实时应用

前面提到了有关个人网站的实时在线人数问题,本文要讨论的是如何自己来实现一个这样的统计服务。因为网站也同时部署在Github上,海外用户访问Github镜像网站的访问日志Pi是拿不到的,这怎么办? Google Channel Service Google Channel Service允许应用和GAE (Google App Engine) 保持一个长连接,允许应用实时发送消息给JavaScript客户端,而不用让客户端用效率很低的定时轮询获取新消息。这个服务是允许有多个发布者和多个订阅者,也能创建多个主题来关联发布者和订阅者。 使用这个服务分两步: 客户端请求服务器端(部署在GAE上)获取一个Channel的Token: 客户端根据Channel Token和服务器建立长连接,并开始接收消息,这时其它的客户端(或服务器端)可以想这个通道发送消息 在线人数统计实现 在页面上部署beacon 通常的网站流量统计是依赖部署在页面上的beacon(Javascript或图片标签)来实现的,这样做的好处是可以直接过滤掉一些机器流量,并且可以将日志集中存储在日志收集服务器上,和网站分离开。 于是可以利用GAE实现一个简单的Beacon服务,这里采用Go语言来实现,用Java或Python也是可以的。 package counter import ( "encoding/base64" "fmt" "time" "net/http" "appengine" "appengine/channel" ) var GIF []byte const ( TOPIC = "counter" ) func init() { GIF, _ = base64.StdEncoding.DecodeString("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7") http.HandleFunc("/beacon.gif", handler) http.HandleFunc("/new_token", handler_new_token) } func handler(w http.ResponseWriter, r *http.Request) { context := appengine.NewContext(r) now := time.Now() expire := now.AddDate(30, 0, 0) zcookie, _ := r. [Read More]

Arduino初试

今天拿到一块Arduino UNO R3板,迫不及待就开始试用了。相比Raspberry Pi是一个全能的电脑,Arduino则是个硬件开源的单片机,因为开源,资料和配件网上就很很多了,也就容易让初学者上手了。 Arduino特点: 开源,硬件标准化,配套传感器等模块很多; 结构简单 实时系统,稳定,启动只要0.5秒 Arduino IDE 下载Arduino IDE 上电测试 用USB线接在电脑USB口,然后在GND和PIN 13上插一个二极管,注意二极管正极插在PIN 13上, 如下图: (注:还应该串联一个300欧姆的限流电阻才保险!) 上传代码 在Arduino IDE编辑好下面的代码,然后点Upload后就会运行了,会看到LED一闪一闪。 /* Blink Turns on an LED on for one second, then off for one second, repeatedly. This example code is in the public domain. */ // Pin 13 has an LED connected on most Arduino boards. // give it a name: int led = 13; // the setup routine runs once when you press reset: void setup() { // initialize the digital pin as an output. [Read More]

并发编程之内存屏障

原文地址:http://mechanical-sympathy.blogspot.com/2011/07/memory-barriersfences.html 或 http://ifeve.com/memory-barriersfences/ 关键词:Load Barrier, Store Barrier, Full Barrier 本文我将和大家讨论并发编程中最基础的一项技术:内存屏障或内存栅栏,也就是让一个CPU处理单元中的内存状态对其它处理单元可见的一项技术。 CPU使用了很多优化技术来达成一个事实:CPU执行单元的速度要远超主存访问速度。在我上一篇文章 “Write Combing - 合并写"中我已经介绍了其中的一项技术。CPU避免内存访问延迟最常见的技术是将指令管道化,然后尽量重排这些管道的执行以最大利用缓存而把因为缓存未命中引起的延迟降到最小。 当一个程序执行时指令是否被重排并不重要,只要最终的结果是一样的。例如,在一个循环里,如果循环体内没用到这个计数器,循环的计数器什么时候更新(在循环开始,中间还是最后)并不重要。编译器和CPU可以自由的重排指令以最佳的利用CPU,只要下一次循环前更新该计数器即可。并且在循环执行中,这个变量可能一直存在寄存器上,并没有被推到缓存或主存,这样这个变量对其他CPU来说一直都是不可见的。 CPU核内部包含了多个执行单元。例如,现代Intel CPU包含了6个执行单元,可以组合进行算术运算,逻辑条件判断及内存操作。每个执行单元可以执行上述任务的某种组合。这些执行单元是并行执行的,这样指令也就是在并行执行。但如果站在另一个CPU角度看,这也就产生了程序顺序的另一种不确定性。 最后,当一个缓存失效发生时,现代CPU可以先假设一个内存载入的值并根据这个假设值继续执行,直到内存载入返回确切的值。 CPU核 | V 寄存器 | V 执行单元 -> Load/Store缓冲区->L1 Cache --->L3 Cache-->内存控制器-->主存 | | +-> Write Combine缓冲区->L2 Cache ---+ 代码顺序并不是真正的执行顺序,CPU和编译器可以各种优化只要有空间提高性能。缓存和主存的读取会利用load, store和write-combining缓冲区来缓冲和重排。这些缓冲区是查找速度很快的关联队列,当一个后来发生的load需要读取上一个store的值,而该值还没有到达缓存,查找是必需的,上图描绘的是一个简化的现代多核CPU,从上图可以看出执行单元可以利用本地寄存器和缓冲区来管理和缓存子系统的交互。 在多线程环境里需要使用技术来使得程序结果尽快可见。这篇文章里我不会涉及到 Cache Conherence 的概念。请先假定一个事实:一旦内存数据被推送到缓存,就会有消息协议来确保所有的缓存会对所有的共享数据同步并保持一致。这个使内存数据对CPU核可见的技术被称为内存屏障或内存栅栏。 内存屏障提供了两个功能。首先,它们通过确保从另一个CPU来看屏障的两边的所有指令都是正确的程序顺序,而保持程序顺序的外部可见性;其次它们可以实现内存数据可见性,确保内存数据会同步到CPU缓存子系统。 大多数的内存屏障都是复杂的话题。在不同的CPU架构上内存屏障的实现非常不一样。相对来说Intel CPU的强内存模型比DEC Alpha的弱复杂内存模型(缓存不仅分层了,还分区了)更简单。因为x86处理器是在多线程编程中最常见的,下面我尽量用x86的架构来阐述。 Store Barrier Store屏障,是x86的”sfence“指令,强制所有在store屏障指令之前的store指令,都在该store屏障指令执行之前被执行,并把store缓冲区的数据都刷到CPU缓存。这会使得程序状态对其它CPU可见,这样其它CPU可以根据需要介入。一个实际的好例子是Disruptor中的BatchEventProcessor。当序列Sequence被一个消费者更新时,其它消费者(Consumers)和生产者(Producers)知道该消费者的进度,因此可以采取合适的动作。所以屏障之前发生的内存更新都可见了。 private volatile long sequence = RingBuffer.INITIAL_CURSOR_VALUE; // from inside the run() method T event = null; long nextSequence = sequence. [Read More]