Go协程安全

多路复用 Go语言中提供了一个关键字select,通过select可以监听channel上的数据流动。select的用法与switch语法类似,由select开始一个新的选择块,每个选择条件由case语句来描述。只不过,select的case有比较多的限制,其中最大的一条限制就是每个case语句里必须是一个IO操作。 select 语法如下: go 1 2 3 4 5 6 7 8 select { case <-chan1: // 如果chan1成功读到数据,则进行该case处理语句 case chan2 <- 1: // 如果成功向chan2写入数据,则进行该case处理语句 default: // 如果上面都没有成功,则进入default处理流程 } 在一个select语句中,会按顺序从头至尾评估每一个发送和接收的语句;如果其中的任意一语句可以继续执行(即没有被阻塞),那么就从那些可以执行的语句中任意选择一条来使用。如果没有任意一条语句可以执行(即所有的通道都被阻塞),那么有两种可能的情况:⑴ 如果给出了default语句,那么就会执行default语句,同时程序的执行会从select语句后的语句中恢复。⑵ 如果没有default语句,那么select语句将被阻塞,直到至少有一个channel可以进行下去。 在一般的业务场景下,select不会用default,当监听的流中再没有数据,IO操作就 会阻塞现象,如果使用了default,此时可以出让CPU时间片。如果使用了default 就形成了非阻塞状态,形成了忙轮训,会占用CPU、系统资源。 阻塞与非阻塞使用场景 阻塞: 如:在监听超时退出时,如果100秒内无操作,择退出,此时添加了default会形成忙轮训,超时监听变成了无效。 非阻塞: 如,在一个只有一个业务逻辑处理时,主进程控制进程的退出。此时可以使用default。 定时器 Go语言中定时器的使用有三个方法 time.Sleep() time.NewTimer() 返回一个时间的管道, time.C 读取管道的内容 time.After(5 * time.Second) 封装了time.NewTimer(),反回了一个 time.C的管道 示例 go 1 2 3 select { case <-time.After(time.Second * 10): } 锁和条件变量 Go语言中为了解决协程间同步问题,提供了标准库代码,包sync和sync/atomic中。 互斥锁 互斥锁是传统并发编程对共享资源进行访问控制的主要手段,它由标准库sync中的Mutex结构体类型表示。sync.Mutex类型只有两个公开的指针方法,Lock和Unlock。Lock锁定当前的共享资源,Unlock进行解锁。 go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package main import ( "fmt" "runtime" "sync" "time" ) var mutex sync....

 ·  · 

Go协程通讯

channel是Go语言中的一个核心数据类型,channel是一个数据类型,主要用来解决协程的同步问题以及协程之间数据共享(数据传递)的问题。在并发核心单元通过它就可以发送或者接收数据进行通讯,这在一定程度上又进一步降低了编程的难度。 goroutine运行在相同的内存地址空间,channel可以避开所有内存共享导致的坑;通道的通信方式保证了同步性。数据通过channel:同一时间只有一个协程可以访问数据:所以不会出现数据竞争,确保并发安全。 channel的定义 channel是对应make创建的底层数据结构的引用。 创建语法: make(chan Type, capacity) go 1 2 3 4 5 6 7 channel := make(chan bool) //创建一个无缓冲的bool型Channel
,等价于make(chan Type, 0) channel := make(chan bool, 1024) //创建一个有缓冲,切缓冲区为1024的bool型Channel
channel <- x //向一个Channel发送一个值 <- channel //从一个Channel中接收一个值 x = <- channel //从Channel c接收一个值并将其存储到x中 x, ok = <- channel //从Channel接收一个值,如果channel关闭了或没有数据,那么ok将被置为false channel是一个引用类型,当复制一个channel或用于函数参数传递时,我们只是拷贝了一个channel引用,因此调用者和被调用者将引用同一个channel对象。和其它的引用类型一样,channel的零值(定义未初始化)也是nil。 在默认情况下,channel接收和发送数据都是阻塞的,(channel <- 1,写端写数据,读端不在读。写端阻塞; str := <-channel 读端读数据, 同时写端不在写,读端阻塞。)除非另一端已经准备好,这样就使得goroutine同步变的更加的简单,而不需要显式的lock。 示例 go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package main import ( "fmt" "runtime" "time" ) var c = make(chan int32) func printstr(s string) { for _, value := range s { fmt....

 ·  · 

go语言的并发编程gorouting

并行和并发 并发编程是指在一台处理器上“同时”处理多个任务。 宏观并发:在一段时间内,有多个程序在同时运行。 微观并发:在同一时刻只能有一条指令执行,但多个程序指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个程序快速交替的执行。 并行 parallel:同一时刻,多条指令在多个处理器上同时执行。 并发 concurrency:在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,通过cpu时间片轮转使多个进程快速交替的执行。 通俗来讲,并行是两组队列同时使用一个进程;并发是两个队列分别交替使用两个进程 进程并发 程序,以Go语言为例,是指编译好的二进制文件,在磁盘上,不占用系统资源(cpu、内存、打开的文件、设备、锁….) 进程,是一个抽象的概念,与操作系统原理联系紧密。以Go语言为例,将编译好的程序运行起来,在内存空间中形成一个独立的内存体,内存体有自己的独立空间,上级挂靠单位是操作系统。 进程是操作系统进行资源分配和调度的一个独立单位,一般由程序,数据集合和进程控制块三部分组成。 程序:描述进程完成的功能,是控制进程执行的指令集; 数据集合:程序在执行时所需要的数据和工作区; 程序控制块PCB:Program Control Block,包含进程的描述信息和控制信息,是进程存在的唯一标志。 进程是活跃的程序,占用系统资源。在内存中执行。同一个程序也可以加载为不同的进程(彼此之间互不影响) 进程状态 进程基本的状态有5种。分别为初始态,就绪态,运行态,挂起态与终止态。其中初始态为进程准备阶段,常与就绪态结合来看。 线程的任务调度 大部分操作系统的任务调度是采用时间片轮转的抢占式调度方式。 时间片轮转是指,在一个进程中,当线程任务执行几毫秒后,由操作系统内核进行调度,通过硬件计数器终端处理器,让线程强行暂停,并将该线程的寄存器放入内存中,通过查看线程列表决定接下来执行哪一个线程,并从内存中恢复该线程的寄存器,最后恢复该线程的执行,从而去执行下一个任务。 在时间片轮转中,任务执行那段时间叫做时间片,任务正在执行时的状态叫运行状态,被暂停的线程任务状态叫做就绪状态,意为等待下一个属于它的时间片的到来。 由于CPU的执行效率非常高,(i5 6600 约200亿/秒,奔腾4 约13亿/秒)CPU preformance 时间片非常短,在各个任务之间快速地切换,给人的感觉就是多个任务在“同时进行”,这也就是我们所说的并发。多任务运行过程的示意图如下: 进程实现并发时会出现的问题呢 孤儿进程: 父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为init进程,称为init进程领养孤儿进程。 僵尸进程: 进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。 线程并发 在早期操作系统当中,没有线程的概念,进程是最小分配资源与执行单位,可以看做是一个进程中只有一个线程,故进程即线程。所以线程LWP被称为::Lightweight process,轻量级的进程,是程序执行中一个单一的顺序控制流程,在Linux操作系统下,线程的本质仍是进程。 线程有独立的PCB,但没有独立的地址空间,各个线程之间共享程序的内存空间。 进程和线程的区别 进程:最小分配资源单位,可看成是只有一个线程的进程。 线程:最小的执行单位 一个进程由一个或多个线程组成 进程之间相互独立,同一进程下的各个线程之间共享程序的内存空间 协程并发 协程 coroutines,是一种基于线程之上,但又比线程更加轻量级的存在,这种由程序来管理的轻量级线程叫做『用户空间线程』,具有对内核来说不可见的特性。 多数语言在语法层面并不直接支持协程,而是通过库的方式支持,但用库的方式支持的功能也并不完整,比如仅仅提供协程的创建、销毁与切换等能力。如果在这样的轻量级线程中调用一个同步 IO 操作,比如网络通信、本地文件读写,都会阻塞其他的并发执行轻量级线程,从而无法真正达到轻量级线程本身期望达到的目标。 协程和线程的区别 占用资源:线程,初始单位为1MB,固定不可变;协程初始一般为 2KB,可随需要而增大。 调度:线程,由操作系统内核完成,协程,由用户完成。 性能: 线程,占用资源高,频繁创建销毁带来性能问题。占用资源小,不会带来严重的性能问题。 数据: 线程,多线程需要锁机制确保数据一致性和可见性;而线程因为只有一个进程,不存在同时读/写冲突,协程中控制共享数据不用加锁,顾执行效率较线程高。 Go并发 goroutine Go语言在语言级别支持协程,叫goroutine。Go语言标准库提供的所有系统调用操作(包括所有同步IO操作),都会出让CPU给其他goroutine。这种轻量级线程的切换管理不依赖于系统的线程和进程,也不需要依赖于CPU的核心数量。 Go语言为并发编程而内置的上层API基于顺序通信进程模型CSP(communicating sequential processes)。这就意味着显式锁都是可以避免的,因为Go通过相对安全的通道发送和接受数据以实现同步,这大大地简化了并发程序的编写。 Go语言中的并发程序主要使用两种手段来实现。goroutine和channel。 什么是goroutine Go语言作者Rob Pike说, “Goroutine是一个与其他goroutines并发运行在同一地址空间的Go函数或方法。一个运行的程序由一个或更多个goroutine组成。它与线程、协程、进程等不同。它是一个goroutine*。...

 ·  · 

Go语言数据类型转换

string in mutual conversion go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 // int to int64 m := int64(n) // int64 to int n := int(m) // string to int int,err := strconv.Atoi(string) // string to int64 int64, err := strconv.ParseInt(string, 10, 64) // int to string string := strconv....

 ·  · 

Go中的signal处理

什么是信号 在计算机科学中,信号是Unix、类Unix以及其他POSIX兼容的操作系统中进程间通讯的一种有限制的方式。它是一种异步的通知机制,用来提醒进程一个事件已经发生。 当一个信号发送给一个进程,操作系统中断了进程正常的控制流程,如果进程定义了对信号的处理,此时,程序将进入捕获到的信号对应的处理函数,否则执行默认的处理函数。 Linux中信号的介绍 在Linux系统共定义了64种信号,分为两大类:实时信号与非实时信号,1-31为非实时,32-64种为实时信号。 非实时信号: 也称为不可靠信号,为早期Linux所支持的信号,不支持排队,信号可能会丢失, 比如发送多次相同的信号, 进程只能收到一次. 信号值取值区间为1~31; 实时信号: 也称为可靠信号,支持排队, 信号不会丢失, 发多少次, 就可以收到多少次. 信号值取值区间为32~64 Linux操作系统中,在终端上执行 kill -l 便可看到系统定义的所有信号 信号表 POSIX.1-1990标准信号 此表参考自:POSIX信号 信号 值 动作 说明 SIGHUP 1 Term 终端控制进程结束(终端连接断开) SIGINT 2 Term 用户发送INTR字符(Ctrl+C)触发 SIGQUIT 3 Core 用户发送QUIT字符(Ctrl+/)触发 SIGILL 4 Core 非法指令(程序错误、试图执行数据段、栈溢出等) SIGABRT 6 Core 调用abort函数触发 SIGFPE 8 Core 算术运行错误(浮点运算错误、除数为零等) SIGKILL 9 Term 无条件结束程序(不能被捕获、阻塞或忽略) SIGSEGV 11 Core 无效内存引用(试图访问不属于自己的内存空间、对只读内存空间进行写操作) SIGPIPE 13 Term 消息管道损坏(FIFO/Socket通信时,管道未打开而进行写操作) SIGALRM 14 Term 时钟定时信号 SIGTERM 15 Term 结束程序(可以被捕获、阻塞或忽略) SIGUSR1 30,10,16 Term 用户保留 SIGUSR2 31,12,17 Term 用户保留 SIGCHLD 20,17,18 Ign 子进程结束(由父进程接收) SIGCONT 19,18,25 Cont 继续执行已经停止的进程(不能被阻塞) SIGSTOP 17,19,23 Stop 停止进程(不能被捕获、阻塞或忽略) SIGTSTP 18,20,24 Stop 停止进程(可以被捕获、阻塞或忽略) SIGTTIN 21,21,26 Stop 后台程序从终端中读取数据时触发 SIGTTOU 22,22,27 Stop 后台程序向终端中写数据时触发 更多的信号说明请查阅man7...

 ·  ·