go语言编程 要点总结(四)并发编程

并发基础

每个进程只有一个执行上下文,一个调用栈一个堆,操作系统在调度进程时,会保存被调度进程的上下文环境,等待该进程获得时间片后,再恢复进程上下文。

并发价值

并发能更客观的表现问题模型 (图形界面与后台处理)

并发能充分利用CPU核心的优势,提高程序的执行效率

并发能充分利用CPU与其他硬件设备固有的异步性(中断触发)

实现并发的方式

多进程,操作系统层面的并发,开销最大的方式。好处是简单,进程间互不影响,坏处是开销大,所有进程都是由内核管理的

多线程,在大部分操作系统上都属于系统层面的并发,最有效,开销比进程小,在高并发模式下,效率会有影响

基于回调的非阻塞/异步IO,高并发情况下多线程会很快耗尽服务器的内存和CPU,通过事件驱动的方式使系统运转,坏处是编程复杂,把流程做了分割,对问题本身的反应不够自然

协程,本质上是用户态线程,不需要操作系统进行抢占式调度,而在真正实现中寄存于线程中,因此系统开销极小。提高任务的并发性,避免多线程缺点。优点是编程简单,结构清晰,缺点是需要语言支持。

内存共享系统,线程之间通过共享内存的方式,加锁

消息传递系统,线程之间通过消息通信,发送消息时对状态进行复制,并且在消息传递的边界上交出这个状态的所有权。由于复制,性能并不优越。

协程

协程可以轻松创建上百万而不会导致系统资源衰竭,线程或进程最多不能超过1万

多数语言只提供轻量级线程创建,销毁和切换能力,任何同步IO操作都会阻塞并发执行的轻量级线程

goroutine

go语言的goroutine,在任何系统调用时都会出让CPU给其他goroutine

定义一个函数,通过go关键字调用,这次调用就会在一个新的gotoutine中并发执行,当被调用的函数返回时,这个goroutine自动结束,如果这个函数有返回值,这个返回值会被丢弃

并发通信

工程上,有两种常见的并发通信模型:共享数据和消息

设计上遵循的通信原则:不要通过共享来通信,而是通过通信来共享

go语言可以支持共享内存和锁,但是他提供消息机制

消息机制认为每个并发单元是字包含的独立的个体,并且都有自己的变量,不同并发单元不共享变量,每个并发单元的输入和输出只有一种就是消息

channel

channel是goroutine间的通信方式

channel是进城内通信方式,通过channel传递对象过程和调用函数时传递参数行为比较一致

channel是类型相关,一个channel职能传递一种类型,需要在声明channel时指定

通过ch<-把值写入channel,这个写入操作在<-ch在读取之前是阻塞的,这样就可以确保所有channel执行完毕之后才返回。

var chanName chan ElementType ElementType是指channel传递元素的类型

var ch chan int int类型的channel

var m map[string] chan bool 声明一个map,元素是bool型的channel

ch := make(chan int) 声明并初始化一个int型的channel

ch <- value 写入,向channel写入数据会导致程序阻塞,直到有其他goroutine从这个channel读取数据

value := <- ch 从channel读取数据,如果之前没有写入数据也会阻塞

select

select用法和switch相似,每个选择条件用case语句描述,case的每一条语句里必须是一个IO操作

如果多个case都满足条件,选取哪个先执行是随机的

dafault,当监听的channel都没有准备好时,默认执行的,select不再阻塞等待channel

缓冲机制

可以指定channel的缓冲区大小,通过make(chan int, 1024)第二个参数

缓冲写满之前,不会阻塞

读取和常规非缓冲channel相同,但可以使用for和range来读取

for i := range c {

........

}

超时机制

不能永久等待,否则可能出现死锁

go语言没有直接提供超时处理机制,但可以利用select机制

timeout := make(chan bool, 1)

go func() {

time.Sleep(1e9)

timeout <- true

}()

select {

case <- ch:

case <- timeout:

// 一直没有从ch中读取到数据,但从timeout中读取到数据

}

这是在go语言中避免channel通信超时最有效办法

channel的传递

go语言中channel是原生类型,自身也可以通过channel传递

利用这个特定来实现管道:

type PipeData struct {

value int

handler func(int) int

next chan int

}

func handle(queue chan *PipeData) {

for data := range queue {

data.next <- data.handler(data.value)

}

}

单向channel

将一个chanel变量来那个传递给一个函数时,可以通过将其指定为单向channel,从而限制该函数中对此channel的操作

var ch1 chan int 常规channel

var ch2 char<- float64 单向写channel

var ch3 <-cha int 单向读channel

channel初始化和类型转换

ch4 := makee(chan int)

ch5 := <-chan int(ch4)

ch6 := chan<- int(ch4)

单向channel的意义和c中的const类似,最小权限原则,避免没必要的使用泛滥问题

关闭channel

close(ch)

通过x, ok := <- ch来判断一个ch是否已经关闭

应该在生产者的地方关闭channel,而不是消费者,否则容易引起panic

多核并行化

当前版本的go编译器还不能智能的发现和利用多核的优势,有可能gotoutine都运行在一个cpu上,一个goroutine执行时,其他的goroutine等待

go语言升级之前,可以设置环境变量GOMAXPROCS来控制使用多少个CPU

或者在代码中启动goroutine之前通过runtime.GOMAXPROCS(CPUNUM)来设置

runtime包还提供了NumCPU来获取核心数

出让时间片

每个goroutine可以主动出让时间片,通过runtime包的函数Gosched()

退出当前goroutine

Goexit,退出当前goroutine,但是defer还会继续调用

同步

即使成功使用了channel有时也难以避免在goroutine之间共享数据

同步锁

sync.Mutex和sync.RWMutex

Lock()和RLock() 对应的Unlock()和RUnlock()

例子

var l sync.Mutex

func foo() {

l.Lock()

defer l.Unlock()

}

全局唯一性操作

Once类型来保证全局唯一性操作

func setup() {

}

var once sync.Once

once.Do(setup)

Do方法可以保证在全局范围内只调用指定函数一次,而且其他goroutine在调用到此语句时,将会先备阻塞,直至全局唯一的调用结束

sync包包含一个atomic子包,提供一些基础数据类型的原子操作函数

func CompareAndSwapUint64(val *uint64, old, new uint64)(swapped bool)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值