Golang系列(三)之并发编程
本文个人博客地址:http://www.huweihuang.com/article/golang/golang-concurrent-programming/
(一)并发基础
1.概念
并发意味着程序在运行时有多个执行上下文,对应多个调用栈。
并发与并行的区别:
并发的主流实现模型:
实现模型
|
说明
|
特点
|
---|---|---|
多进程 | 操作系统层面的并发模式 | 处理简单,互不影响,但开销大 |
多线程 | 系统层面的并发模式 | 有效,开销较大,高并发时影响效率 |
基于回调的非阻塞/异步IO | 多用于高并发服务器开发中 | 编程复杂,开销小 |
协程 | 用户态线程,不需要操作系统抢占调度,寄存于线程中 | 编程简单,结构简单,开销极小,但需要语言的支持 |
共享内存系统:线程之间采用共享内存的方式通信,通过加锁来避免死锁或资源竞争。
消息传递系统:将线程间共享状态封装在消息中,通过发送消息来共享内存,而非通过共享内存来通信。
2.协程
执行体是个抽象的概念,在操作系统中分为三个级别:进程(process),进程内的线程(thread),进程内的协程(coroutine,轻量级线程)。协程的数量级可达到上百万个,进程和线程的数量级最多不超过一万个。Go语言中的协程叫goroutine,Go标准库提供的调用操作,IO操作都会出让CPU给其他goroutine,让协程间的切换管理不依赖系统的线程和进程,不依赖CPU的核心数量。
3.并发通信
并发编程的难度在于协调,协调需要通过通信,并发通信模型分为共享数据和消息。共享数据即多个并发单元保持对同一个数据的引用,数据可以是内存数据块,磁盘文件,网络数据等。数据共享通过加锁的方式来避免死锁和资源竞争。Go语言则采取消息机制来通信,每个并发单元是独立的个体,有独立的变量,不同并发单元间这些变量不共享,每个并发单元的输入输出只通过消息的方式。
(二)goroutine
//定义调用体
func Add(x,y int ){
z:=x+y
fmt.Println(z)
}
//go关键字执行调用,即会产生一个goroutine并发执行
//当函数返回时,goroutine自动结束,如果有返回值,返回值会自动被丢弃
go Add(1,1)
//并发执行
func main(){
for i:=0;i<10;i++{ //主函数启动了10个goroutine,然后返回,程序退出,并不会等待其他goroutine结束
go Add(i,i) //所以需要通过channel通信来保证其他goroutine可以顺利执行
}
}
|
(三)channel
channel就像管道的形式,是goroutine之间的通信方式,是进程内的通信方式,跨进程通信建议用分布式系统的方法来解决,例如Socket或http等通信协议。channel是类型相关,即一个channel只能传递一种类型的值,在声明时指定。
1、基本语法
//1、channel声明,声明一个管道chanName,该管道可以传递的类型是ElementType
//管道是一种复合类型,[chan ElementType],表示可以传递ElementType类型的管道[类似定语从句的修饰方法]
var chanName chan ElementType
var ch chan int //声明一个可以传递int类型的管道
var m map[string] chan bool //声明一个map,值的类型为可以传递bool类型的管道
//2、初始化
ch:=make(chan int ) //make一般用来声明一个复合类型,参数为复合类型的属性
//3、管道写入,把值想象成一个球,"<-"的方向,表示球的流向,ch即为管道
//写入时,当管道已满(管道有缓冲长度)则会导致程序堵塞,直到有goroutine从中读取出值
ch <- value
//管道读取,"<-"表示从管道把球倒出来赋值给一个变量
//当管道为空,读取数据会导致程序阻塞,直到有goroutine写入值
value:= <-ch
//4、每个case必须是一个IO操作,面向channel的操作,只执行其中的一个case操作,一旦满足则结束select过程
//面向channel的操作无非三种情况:成功读出;成功写入;即没有读出也没有写入
select{
case <-chan1:
//如果chan1读到数据,则进行该case处理语句
case chan2<-1:
//如果成功向chan2写入数据,则进入该case处理语句
default :
//如果上面都没有成功,则进入default处理流程
}
|
2、缓冲和超时机制
//1、缓冲机制:为管道指定空间长度,达到类似消息队列的效果
c:=make(chan int ,1024) //第二个参数为缓冲区大小,与切片的空间大小类似
//通过range关键字来实现依次读取管道的数据,与数组或切片的range使用方法类似
for i :=range c{
fmt.Println( "Received:" ,i)
}
//2、超时机制:利用select只要一个case满足,程序就继续执行而不考虑其他case的情况的特性实现超时机制
timeout:=make(chan bool ,1) //设置一个超时管道
go func(){
time .Sleep(1e9) //设置超时时间,等待一秒钟
timeout<- true //一分钟后往管道放一个true的值
}()
//
select {
case <-ch: //如果读到数据,则会结束select过程
//从ch中读取数据
case <-timeout: //如果前面的case没有调用到,必定会读到true值,结束select,避免永久等待
//一直没有从ch中读取到数据,但从timeout中读取到了数据
}
|
3、channel的传递
//1、channel的传递,来实现Linux系统中管道的功能,以插件的方式增加数据处理的流程
type PipeData struct {
value int
handler func( int ) int //handler是属性?
next chan int //可以把[chan int]看成一个整体,表示放int类型的管道
}
func handler(queue chan *PipeData){ //queue是一个存放*PipeDate类型的管道,可改变管道里的数据块内容
for data:=range queue{ //data的类型就是管道存放定义的类型,即PipeData
data.next <- data.handler(data.value) //该方法实现将PipeData的value值存放到next的管道中
}
}
//2、单向channel:只能用于接收或发送数据,是对channel的一种使用限制
//单向channel的声明
var ch1 chan int //正常channel,可读写
var ch2 chan<- int //单向只写channel [chan<- int]看成一个整体,表示流入管道
var ch3 <-chan int //单向只读channel [<-chan int]看成一个整体,表示流出管道
//管道类型强制转换
ch4:=make(chan int ) //ch4为双向管道
ch5:=<-chan int (ch4) //把[<-chan int]看成单向只读管道类型,对ch4进行强制类型转换
ch6:=chan<- int (ch4) //把[chan<- int]看成单向只写管道类型,对ch4进行强制类型转换
func Parse(ch <-chan int ){ //最小权限原则
for value:=range ch{
fmt.Println( "Parsing value" ,value)
}
}
//3、关闭channel,使用内置函数close()函数即可
close(ch)
//判断channel是否关闭
x,ok:=<-ch //ok==false表示channel已经关闭
if !ok { //如果channel关闭,ok==false,!ok==true
//执行体
}
|
(四)多核并行化与同步
//多核并行化
runtime.GOMAXPROCS(16) //设置环境变量GOMAXPROCS的值来控制使用多少个CPU核心
runtime.NumCPU() //来获取核心数
//出让时间片
runtime.Gosched() //在每个goroutine中控制何时出让时间片给其他goroutine
//同步
//同步锁
sync.Mutex //单读单写:占用Mutex后,其他goroutine只能等到其释放该Mutex
sync.RWMutex //单写多读:会阻止写,不会阻止读
RLock() //读锁
Lock() //写锁
RUnlock() //解锁(读锁)
Unlock() //解锁(写锁)
//全局唯一性操作
//once的Do方法保证全局只调用指定函数(setup)一次,其他goroutine在调用到此函数是会阻塞,直到once调用结束才继续
once.Do(setup)
|
Golang系列(三)之并发编程相关推荐
- php三要素,并发编程三要素:原子性,有序性,可见性
并发编程三要素 **原子性:**一个不可再被分割的颗粒.原子性指的是一个或多个操作要么全部执行成功要么全部执行失败. 有序性: 程序执行的顺序按照代码的先后顺序执行.(处理器可能会对指令进行重排序) ...
- python多线程并发编程技术_三 python并发编程之多线程-理论
一 什么是线程 在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程 线程顾名思义,就是一条流水线工作的过程,一条流水线必须属于一个车间,一个车间的工作过程是一个进程 车间负责把资源整合 ...
- java高并发(三)并发编程的基础
CPU多级缓存 为什么需要CPU缓存? 原因是,CPU的频率太快了,快到主存跟不上,这样在处理器时钟周期内,CPU常常需要等待主存,浪费资源.所以cache的出现,是为了缓解CPU和内存之间速度的不匹 ...
- golang经典书籍--go并发编程
- Java并发编程系列
Java并发编程系列 2018-03-08 Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) ...
- Java多线程系列(七):并发容器的原理,7大并发容器详解、及使用场景
之前谈过高并发编程系列: 高并发编程系列:4种常用Java线程锁的特点,性能比较.使用场景 高并发编程系列:CountDownLatch.Semaphore等4大并发工具类详解 高并发编程系列:4大J ...
- Java并发编程|第二篇:线程生命周期
文章目录 系列文章 1.线程的状态 2.线程生命周期 3.状态测试代码 4.线程终止 4.1 线程执行完成 4.2 interrupt 5.线程复位 5.1interrupted 5.2抛出异常 6. ...
- 并发编程(七)好用的线程池ThreadPoolExecutor
并发编程专栏系列博客 并发编程(一)python并发编程简介 并发编程(二)怎样选择多线程多进程和多协程 并发编程(三)Python编程慢的罪魁祸首.全局解释器锁GIL 并发编程(四)如何使用多线程, ...
- 并发编程(五)python实现生产者消费者模式多线程爬虫
并发编程专栏系列博客 并发编程(一)python并发编程简介 并发编程(二)怎样选择多线程多进程和多协程 并发编程(三)Python编程慢的罪魁祸首.全局解释器锁GIL 并发编程(四)如何使用多线程, ...
- 阿里蚂蚁金服Java岗330道面试题(性能调优+微服务+并发编程+开源框架+分布式)
前言 2019年还有不到2个月的时间就结束了,这一你,你收获了多少? 前段时间一直有粉丝问我,有没有今年一些大厂Java面试题总结?最新抽时间整理了一些,分享给大家,大家一起共享学习! 一.性能调优 ...
最新文章
- C#帮助类:MD5加密
- MITRE:利用微生物组时间序列数据推断与宿主状态变化相关的特征
- spring boot的热加载(hotswap)
- Maven整合SSM框架(maven+spring+springmvc+mybatis)
- 牛客多校第六场-H-Pair
- 计算机网络基础知识,仅此一篇足矣
- jquery html data属性,jQuery Mobile Data 属性
- linux禁用IPv6地址
- TensorFlow tf.keras.callbacks.CSVLogger
- 下列哪个适合做链栈_外贸企业如何做Google推广?自然排名和付费广告哪个更适合你?...
- vector的常用总结
- Android 开发性能优化
- Chrome 地址栏如何设置显示 http/https 和 www
- 制衣软件测试自学,服装检验作业指导书.doc
- python发送短信验证码(互亿无线)
- php 常见的视频格式转换
- MATLAB中load用法
- TVS 瞬态抑制二极管指南
- leetcode 5855. 找出数组中的第 K 大整数(C++、java、python)
- win10便签常驻桌面_做备忘录,用win10自带的便笺工具就可以了,免费又方便
热门文章
- sql语句 isnull(列名,'')='' /STUFF的意思
- websql使用实例
- logistics模型的训练
- DevExpress学习笔记之如何获取Repository Item的值
- JGrid有用的收藏
- 增值业务综合运营平台(VGOP)
- 【python】Python的基本数据类型之数字类型与字符串类型
- 线程安全的HashMap,TreeMap,ArrayList,TreeSet,Set
- JMETER SLAVE和MASTER 分布式启动压测
- docker hub push_Docker系列-(2) 镜像制作与发布