go并发学习

go极简的25个关键字很给力,让程序员把更多的精力投入到业务逻辑上;反观C++各种奇技无穷仍让你多年后还在感叹它的魔力。 


 概念: 

 并发):1、多个执行实例在单个CPU上交替进行,在逻辑上表现为同一时刻发生,但在物理上是串行的。 

 并行):1、多个执行实例在多个CPU上并行执行。 

  可以这么理解,并发是一种程序算法实现在单个CPU上模拟同一时刻发生的手段,而并行是物理状态同时发生的。 

 GO使用CSP(Communicating Sequential Processes)通信顺序进程用来描述并发系统中的交互模式。 更多并发模型可以参考"http://blog.csdn.net/sunqihui/article/details/5959576#_Toc275504198 

 进程、线程、协程的关系和区别: 

 1)进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。 

 2)线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度(标准线程是的)。 

 3)协程(系统不提供)和线程一样共享堆,不共享栈,协程由程序员在协程的代码里显示调度。 


 关键字: 

go(协程并发执行的关键字), chan(协程同步和通信的关键字), select(监听信道)  


1)并发

  1.1)传统顺序结构下的程序

package main

import (
	"fmt"
	"strconv"
	"time"
)

func output(str string, sec time.Duration) {
	time.Sleep(sec * time.Second)
	fmt.Println(str, time.Now())

}
func main() {
	startTime := time.Now()
	for i := 0; i < 5; i++ {
		output(strconv.Itoa(i), 2)
	}
	time.Sleep(5 * time.Second)
	fmt.Println(time.Now().Sub(startTime))
}


输出如下:

0 2014-12-24 17:38:36.46757138 +0800 CST
1 2014-12-24 17:38:38.47227465 +0800 CST
2 2014-12-24 17:38:40.476292438 +0800 CST
3 2014-12-24 17:38:42.478405724 +0800 CST
4 2014-12-24 17:38:44.480628443 +0800 CST
15.019152527s

可以观察这个顺序结构下的所耗时间,很符合预期在5次循环中分别执行一个函数,每个函数休眠2秒,加上最后5秒正好正好是15秒


1.2) 并发模式

 

package main

import (
	"fmt"
	"strconv"
	"time"
)

func output(str string, sec time.Duration) {
	time.Sleep(sec * time.Second)
	fmt.Println(str, time.Now())

}
func main() {
	startTime := time.Now()
	for i := 0; i < 5; i++ {
		go output(strconv.Itoa(i), 2)
	}
	time.Sleep(5 * time.Second)
	fmt.Println(time.Now().Sub(startTime))
}

 输出如下: 

0 2014-12-25 14:19:37.743704267 +0800 CST

1 2014-12-25 14:19:37.744008779 +0800 CST 

2 2014-12-25 14:19:37.744030233 +0800 CST

3 2014-12-25 14:19:37.74404259 +0800 CST

4 2014-12-25 14:19:37.744054005 +0800 CST 

5.003290751s

可以看到总耗时间为5秒精确到纳秒级别下的偏差,注意这里5个并发休眠为2秒小于MAIN主协程所以总共耗时也只有5秒,如果并发休眠时间大于主协程休眠时间那么则主线程退出,也就看不到并发函数的输出了。

1.3)并行模式(并行一定属于并发,但并发未必是并行)

package main


import (
"fmt"
"strconv"
"time"
)


func output(str string, sec time.Duration) {
time.Sleep(sec * time.Second)
for i := 0; i < 5; i++ {
fmt.Println(i, time.Now())

}
func main() {
startTime := time.Now()
for i := 0; i < 5; i++ {
go output(strconv.Itoa(1), 2)
}
time.Sleep(5 * time.Second)
fmt.Println(time.Now().Sub(startTime))
}

输出如下:

0 2014-12-25 00:28:49.367975048 +0800 CST
1 2014-12-25 00:28:49.368483096 +0800 CST
2 2014-12-25 00:28:49.368488316 +0800 CST
3 2014-12-25 00:28:49.368491763 +0800 CST
4 2014-12-25 00:28:49.368497156 +0800 CST
0 2014-12-25 00:28:49.368199199 +0800 CST
1 2014-12-25 00:28:49.368509244 +0800 CST
2 2014-12-25 00:28:49.368512021 +0800 CST
3 2014-12-25 00:28:49.368515648 +0800 CST
4 2014-12-25 00:28:49.368518219 +0800 CST
0 2014-12-25 00:28:49.368221194 +0800 CST
1 2014-12-25 00:28:49.368524406 +0800 CST
2 2014-12-25 00:28:49.368528476 +0800 CST
3 2014-12-25 00:28:49.368534461 +0800 CST
4 2014-12-25 00:28:49.368544931 +0800 CST
0 2014-12-25 00:28:49.368230788 +0800 CST
1 2014-12-25 00:28:49.368583808 +0800 CST
2 2014-12-25 00:28:49.368589128 +0800 CST
3 2014-12-25 00:28:49.368593184 +0800 CST
4 2014-12-25 00:28:49.368597326 +0800 CST
0 2014-12-25 00:28:49.368238338 +0800 CST
1 2014-12-25 00:28:49.368606783 +0800 CST
2 2014-12-25 00:28:49.368610953 +0800 CST
3 2014-12-25 00:28:49.368614906 +0800 CST
4 2014-12-25 00:28:49.368619518 +0800 CST
5.003777317s

可以看它在同一时刻发生是并发没有错,但回到上面的概念与并行不同,并行是多个实例在多个CPU执行,那么我们一般PC电脑或者笔记本都只有一个CPU,怎么能模拟并行的场景呢,这里就要了解一下物理CPU、核数、逻辑CPU的关系。现在的CPU大多是双核之上了,这就意味着通过划分任务和线程可以在多核上实现并行了,可以这么理解多核心对操作系统而言就是多个物理CPU,在有些场景下开启了HT,逻辑CPU=Physical CPU*CORE*HT。


改更上面的例子,使用调度器模拟并行

package main

import (
	"fmt"
	"runtime"
	"strconv"
	"time"
)

func output(str string, sec time.Duration) {
	time.Sleep(sec * time.Second)
	for i := 0; i < 5; i++ {
		fmt.Println(i, time.Now())
	}

}
func main() {
	runtime.GOMAXPROCS(2) // 使用2个核
	startTime := time.Now()
	for i := 0; i < 5; i++ {
		go output(strconv.Itoa(1), 2)
	}
	time.Sleep(5 * time.Second)
	fmt.Println(time.Now().Sub(startTime))
}
输出如下:

0 2014-12-25 00:35:57.594041647 +0800 CST
0 2014-12-25 00:35:57.594219738 +0800 CST
1 2014-12-25 00:35:57.59447752 +0800 CST
1 2014-12-25 00:35:57.594476942 +0800 CST
2 2014-12-25 00:35:57.594483738 +0800 CST
2 2014-12-25 00:35:57.594484792 +0800 CST
3 2014-12-25 00:35:57.594487043 +0800 CST
3 2014-12-25 00:35:57.594492282 +0800 CST
4 2014-12-25 00:35:57.59449858 +0800 CST
4 2014-12-25 00:35:57.59449769 +0800 CST
0 2014-12-25 00:35:57.593993358 +0800 CST
0 2014-12-25 00:35:57.594344213 +0800 CST
1 2014-12-25 00:35:57.5945118 +0800 CST
1 2014-12-25 00:35:57.594515977 +0800 CST
2 2014-12-25 00:35:57.594517485 +0800 CST
2 2014-12-25 00:35:57.594520762 +0800 CST
3 2014-12-25 00:35:57.594528518 +0800 CST
3 2014-12-25 00:35:57.594522068 +0800 CST
4 2014-12-25 00:35:57.594533988 +0800 CST
0 2014-12-25 00:35:57.593980927 +0800 CST
4 2014-12-25 00:35:57.594537845 +0800 CST
1 2014-12-25 00:35:57.594543532 +0800 CST
2 2014-12-25 00:35:57.594548405 +0800 CST
3 2014-12-25 00:35:57.5945527 +0800 CST
4 2014-12-25 00:35:57.594556745 +0800 CST
5.004689835s



2)chan (信号通道)关键字

     chan map slice 使用make初始化,通道为分无缓冲与缓冲方式,它们的区别是无缓冲阻塞当前协程,默认为无缓冲的。

    

 2.1)无缓冲通道

     

package main

import (
	"fmt"
	"strconv"
	"time"
)

var ch chan int = make(chan int)

func output(str string, sec time.Duration) {
	time.Sleep(sec * time.Second)
	fmt.Println(str, time.Now())
	ch <- 1 //向通道发送数据,如果没有其它协程来取,则当前协程阻塞

}
func main() {
	startTime := time.Now()

	go output(strconv.Itoa(1), 2)
	<-ch // 从通道读取数据丢弃,如果通道未有数据则阻塞当前协程,这样main主协程就不会因为并发还没有执行完而退出了
	fmt.Println(time.Now().Sub(startTime))
}
输出如下:

1 2014-12-25 00:05:48.612852729 +0800 CST
   2.014814871s


2.2)死锁状态

package main

var ch chan int = make(chan int)

func main() {
	ch <- 1
	<-ch
}
输出如下:

fatal error: all goroutines are asleep - deadlock!

  如果从通道中读取数据,通道中未有数据则阻塞当前协程; 向通道流入数据,如果没有其它协程来拿走同样阻塞当前协程。 可见无缓冲通道在一个协程下会发生死锁因为没有其它协程来读/取数据。

  

2.3)有缓冲通道

     更改上面死锁的例子  

package main

var ch chan int = make(chan int, 1)

func main() {
	ch <- 1
	<-ch
}
如果通道分配了容量并且没有满的状态下当前不会产生阻塞

package main

var ch chan int = make(chan int, 1)

func main() {
	ch <- 1
	ch <- 2
	
}

由于分配1个容量,流入2个数据超出容量,发生死锁

 

package main

import (
	"fmt"
	"runtime"
	"strconv"
	"time"
)

var ch chan int = make(chan int)

func output(str string, sec time.Duration) {
	time.Sleep(sec * time.Second)
	for i := 0; i < 5; i++ {
		fmt.Println(i, time.Now())
	}
	ch <- 0
}
func main() {
	runtime.GOMAXPROCS(2) // 使用2个核
	startTime := time.Now()
	for i := 0; i < 5; i++ {
		go output(strconv.Itoa(1), 2)
	}
	for i := 0; i < 5; i++ {
		<-ch
	}

	fmt.Println(time.Now().Sub(startTime))
}

输出如下:

0 2014-12-25 01:21:28.530587866 +0800 CST
1 2014-12-25 01:21:28.540051344 +0800 CST
2 2014-12-25 01:21:28.5400612 +0800 CST
3 2014-12-25 01:21:28.54006584 +0800 CST
4 2014-12-25 01:21:28.540069734 +0800 CST
0 2014-12-25 01:21:28.530554048 +0800 CST
1 2014-12-25 01:21:28.540087008 +0800 CST
2 2014-12-25 01:21:28.540093445 +0800 CST
0 2014-12-25 01:21:28.530548904 +0800 CST
3 2014-12-25 01:21:28.540097701 +0800 CST
4 2014-12-25 01:21:28.540101064 +0800 CST
1 2014-12-25 01:21:28.540101514 +0800 CST
0 2014-12-25 01:21:28.530584385 +0800 CST
2 2014-12-25 01:21:28.540123409 +0800 CST
1 2014-12-25 01:21:28.540125945 +0800 CST
3 2014-12-25 01:21:28.540128325 +0800 CST
2 2014-12-25 01:21:28.540130956 +0800 CST
4 2014-12-25 01:21:28.540135045 +0800 CST
3 2014-12-25 01:21:28.540135284 +0800 CST
0 2014-12-25 01:21:28.530586666 +0800 CST
4 2014-12-25 01:21:28.540146074 +0800 CST
1 2014-12-25 01:21:28.54014817 +0800 CST
2 2014-12-25 01:21:28.540152728 +0800 CST
3 2014-12-25 01:21:28.54015693 +0800 CST
4 2014-12-25 01:21:28.540161064 +0800 CST
2.010080495s


2.4)select(监听信道)
package main

import (
	"fmt"
	"runtime"
	"strconv"
	"time"
)

var ch chan int = make(chan int)

func output(str string, sec time.Duration, data int) {
	time.Sleep(sec * time.Second)
	for i := 0; i < 5; i++ {
		fmt.Println(i, time.Now())
	}
	ch <- data
}
func main() {
	timeout := time.After(3 * time.Second)
	runtime.GOMAXPROCS(2) // 使用2个核
	startTime := time.Now()
	for i := 0; i < 5; i++ {
		go output(strconv.Itoa(1), 2, i)
	}
	for t := false; !t; {
		select {
		case c := <-ch:
			fmt.Println(c)
		case <-timeout: // 超时处理
			t = true

		}
	}
	fmt.Println(time.Now().Sub(startTime))
}

输出如下:
0 2014-12-25 11:52:26.770723869 +0800 CST
0 2014-12-25 11:52:26.770760593 +0800 CST
1 2014-12-25 11:52:26.771182791 +0800 CST
1 2014-12-25 11:52:26.77118497 +0800 CST
2 2014-12-25 11:52:26.771188904 +0800 CST
2 2014-12-25 11:52:26.77119193 +0800 CST
3 2014-12-25 11:52:26.771198361 +0800 CST
3 2014-12-25 11:52:26.771192788 +0800 CST
4 2014-12-25 11:52:26.771201893 +0800 CST
4 2014-12-25 11:52:26.771203326 +0800 CST
0 2014-12-25 11:52:26.771001585 +0800 CST
3
2
1 2014-12-25 11:52:26.771216479 +0800 CST
2 2014-12-25 11:52:26.771220719 +0800 CST
0 2014-12-25 11:52:26.770724008 +0800 CST
3 2014-12-25 11:52:26.771224156 +0800 CST
4 2014-12-25 11:52:26.771228775 +0800 CST
1 2014-12-25 11:52:26.771228728 +0800 CST
0
2 2014-12-25 11:52:26.771233756 +0800 CST
3 2014-12-25 11:52:26.771238263 +0800 CST
0 2014-12-25 11:52:26.770912996 +0800 CST
4 2014-12-25 11:52:26.77124198 +0800 CST
1 2014-12-25 11:52:26.771243485 +0800 CST
4
2 2014-12-25 11:52:26.77124763 +0800 CST
3 2014-12-25 11:52:26.771251824 +0800 CST
4 2014-12-25 11:52:26.771255369 +0800 CST
1
3.004506528s

       
2.5 WaitGroup:
采用计数同步多个goroutine; wg.Add() 与wg.Done()匹配,使用wg.Wait()阻塞调用的goroutine.
package main

import (
	"fmt"
	"math/rand"
	"sync"
	"time"
)

var wg sync.WaitGroup

func sub(r int) {
	startTime := time.Now()
	for i := r; i > 0; i-- {//模拟随机耗时
	}
	fmt.Printf("rand:%v time:%v\n", r, time.Now().Sub(startTime))
	wg.Done()
}
func Rand() int { //产生1到1000000000随机数
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	return r.Intn(1000000000)+1

}
func main() {
	startTime := time.Now()
	for i := 1; i <= 5; i++ {
		wg.Add(1)
		go func(r int) {
			sub(r)
		}(Rand())
	}
	wg.Wait()
	fmt.Println(time.Now().Sub(startTime))
}

rand:6432293 time:2.745767ms
rand:86603548 time:577.648948ms
rand:216186094 time:543.073948ms
rand:647216787 time:460.292363ms
rand:488782725 time:201.099371ms
577.942614ms

更多参考:http://blog.golang.org/pipelines  一个计算文件MD5示例 

Once: 只执行一次,可以用在特殊情况下,比如预加载,清理等场景
</pre><pre>
</pre><pre code_snippet_id="561919" snippet_file_name="blog_20150402_11_2794435" name="code" class="cpp">package main

import (
	"sync"
)

var wg sync.WaitGroup
var once sync.Once

func cleanup() {
	println("cleanup")
	wg.Done()
}
func main() {
	for i := 1; i <= 5; i++ {
		wg.Add(1)
		go func() {
			once.Do(cleanup) //由于add了5次,Done只执行了一次,产生死锁错误
			//cleanup() //正确
		}()
	}
	wg.Wait()
}
fatal error: all goroutines are asleep - deadlock!
 goroutine 1 [semacquire]:
sync.(*WaitGroup).Wait(0x6df60)
/usr/local/go/src/sync/waitgroup.go:132 +0x169......................

Mutex(互斥锁)
package main

import (
	"fmt"
	"sync"
	"time"
)

var m sync.Mutex
var hello string

func UnLock() {
	hello = "hello, world"
	time.Sleep(2 * time.Second)
	m.Unlock() //第一次解锁

}
func main() {
	m.Lock()
	fmt.Printf("%#v", m) //sync.Mutex{state:1, sema:0x0}
	go UnLock()

	m.Lock() //等待第一次(UnLock)解锁;如果m已经加锁,则阻塞直到m解锁
	println(hello)

}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值