GoLang之Concurrency再讨论

2013-12-28 wcdj


0 goroutine是否并发的问题

GoLang通过go关键字实现并发操作(真的并发吗?),一个最简单的并发模型:

package main

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

func routine(name string, delay time.Duration) {
	t0 := time.Now()
	fmt.Println(name, " start at ", t0)

	// 停留xxx秒
	time.Sleep(delay)

	t1 := time.Now()
	fmt.Println(name, " end at ", t1)

	// 计算时间差
	fmt.Println(name, " lasted ", t1.Sub(t0))

}

func main() {

	// 生成随机种子, 类似C语言中的srand((unsigned)time(0))生成随机种子
	rand.Seed(time.Now().Unix())

	// To convert an integer number of units to a Duration, multiply
	fmt.Println(time.Duration(5) * time.Second)

	var name string
	for i := 0; i < 3; i++ {
		name = fmt.Sprintf("go_%02d", i) // 生成ID

		// 生成随机等待时间, 从0-4秒
		// ntn returns, as an int, a non-negative pseudo-random number in [0,n) from the default Source. It panics if n <= 0.
		go routine(name, time.Duration(rand.Intn(5))*time.Second)
	}

	// 让主进程停住, 不然主进程退了, goroutine也就退了
	var input string
	fmt.Scanln(&input)
	fmt.Println("done")
}

/*
output:

mba:test gerryyang$ ./rand_t
5s
go_00  start at  2013-12-28 13:25:04.460768468 +0800 HKT
go_01  start at  2013-12-28 13:25:04.460844141 +0800 HKT
go_02  start at  2013-12-28 13:25:04.460861337 +0800 HKT
go_02  end at  2013-12-28 13:25:04.460984329 +0800 HKT
go_02  lasted  122.992us
go_01  end at  2013-12-28 13:25:05.462003787 +0800 HKT
go_01  lasted  1.001159646s
go_00  end at  2013-12-28 13:25:07.461884807 +0800 HKT
go_00  lasted  3.001116339s

done
*/

关于goroutine是否真正并发的问题,耗子叔叔这里是这样解释的:

引用:

关于goroutine,我试了一下,无论是Windows还是Linux,基本上来说是用操作系统的线程来实现的。不过,goroutine有个特性,也就是说,如果一个goroutine没有被阻塞,那么别的goroutine就不会得到执行。这并不是真正的并发,如果你要真正的并发,你需要在你的main函数的第一行加上下面的这段代码:

import runtime
runtime.GOMAXPROCS(n)

本人使用go1.2版本在Linux64,2.6.32内核环境下测试,在上述代码中再添加一个死循环的routine,可以验证上述的逻辑。在没有设置GOMAXPROCS参数时,多个goroutine会出现阻塞的情况;设置GOMAXPROCS参数时,下面的几个routine可以正常执行不会被阻塞。

package main

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

func routine(name string, delay time.Duration) {
    t0 := time.Now()
    fmt.Println(name, " start at ", t0, ", sleep:", delay)

    // 停留xxx秒  
    time.Sleep(delay)

    t1 := time.Now()
    fmt.Println(name, " end at ", t1)

    // 计算时间差  
    fmt.Println(name, " lasted ", t1.Sub(t0))

}

func die_routine() {
    for {
    // die loop
    }
}

func main() {

    // 实现真正的并发
    runtime.GOMAXPROCS(4)

    fmt.Println("set runtime.GOMAXPROCS")

    // 生成随机种子, 类似C语言中的srand((unsigned)time(0))生成随机种子  
    rand.Seed(time.Now().Unix())

    // To convert an integer number of units to a Duration, multiply  
    fmt.Println(time.Duration(5) * time.Second)

    // die routine
    go die_routine()

    var name string
    for i := 0; i < 3; i++ {
        name = fmt.Sprintf("go_%02d", i) // 生成ID  

        // 生成随机等待时间, 从0-4秒  
        // ntn returns, as an int, a non-negative pseudo-random number in [0,n) from the default Source. It panics if n <= 0.  
        go routine(name, time.Duration(rand.Intn(5))*time.Second)
    }

    // 让主进程停住, 不然主进程退了, goroutine也就退了  
    var input string
    fmt.Scanln(&input)
    fmt.Println("done")
}

1 goroutine非并发安全性问题

这是一个经常出现在教科书里卖票的例子,启了5个goroutine来卖票,卖票的函数sell_tickets很简单,就是随机的sleep一下,然后对全局变量total_tickets作减一操作。

package main

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

var total_tickets int32 = 10

func sell_tickets(i int) {
	for {
		// 如果有票就卖
		if total_tickets > 0 {
			time.Sleep(time.Duration(rand.Intn(5)) * time.Millisecond)
			// 卖一张票
			total_tickets--
			fmt.Println("id:", i, " ticket:", total_tickets)
		} else {
			break
		}
	}
}

func main() {

	// 设置真正意义上的并发
	runtime.GOMAXPROCS(4)

	// 生成随机种子
	rand.Seed(time.Now().Unix())

	// 并发5个goroutine来卖票
	for i := 0; i < 5; i++ {
		go sell_tickets(i)
	}

	// 等待线程执行完
	var input string
	fmt.Scanln(&input)
	// 退出时打印还有多少票
	fmt.Println(total_tickets, "done")
}
/*
output:

id: 1  ticket: 8
id: 0  ticket: 8
id: 0  ticket: 7
id: 2  ticket: 5
id: 4  ticket: 6
id: 4  ticket: 3
id: 3  ticket: 3
id: 1  ticket: 1
id: 0  ticket: 2
id: 3  ticket: -1
id: 2  ticket: -1
id: 1  ticket: -2
id: 4  ticket: -3

-3 done
*/

上述例子没有考虑并发安全问题,因此需要加一把锁以保证每个routine在售票的时候数据同步。

package main

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

var total_tickets int32 = 10
var mutex = &sync.Mutex{}

func sell_tickets(i int) {

	for total_tickets > 0 {

		mutex.Lock()
		// 如果有票就卖
		if total_tickets > 0 {
			time.Sleep(time.Duration(rand.Intn(5)) * time.Millisecond)
			// 卖一张票
			total_tickets--
			fmt.Println("id:", i, " ticket:", total_tickets)
		}
		mutex.Unlock()
	}
}

func main() {

	// 设置真正意义上的并发
	runtime.GOMAXPROCS(4)

	// 生成随机种子
	rand.Seed(time.Now().Unix())

	// 并发5个goroutine来卖票
	for i := 0; i < 5; i++ {
		go sell_tickets(i)
	}

	// 等待线程执行完
	var input string
	fmt.Scanln(&input)
	// 退出时打印还有多少票
	fmt.Println(total_tickets, "done")
}
/*
output:

id: 0  ticket: 9
id: 0  ticket: 8
id: 0  ticket: 7
id: 0  ticket: 6
id: 0  ticket: 5
id: 0  ticket: 4
id: 0  ticket: 3
id: 0  ticket: 2
id: 0  ticket: 1
id: 0  ticket: 0

0 done
*/

2 并发情况下的原子操作问题

go语言也支持原子操作。关于原子操作可以参考耗子叔叔这篇文章《 无锁队列的实现》,里面说到了一些CAS – CompareAndSwap的操作。下面的程序有10个goroutine,每个会对cnt变量累加20次,所以,最后的cnt应该是200。如果没有atomic的原子操作,那么cnt将有可能得到一个小于200的数。下面使用了atomic操作,所以是安全的。

package main

import (
	"fmt"
	"sync/atomic"
	"time"
)

func main() {

	var cnt uint32 = 0

	// 启动10个goroutine
	for i := 0; i < 10; i++ {
		go func() {
			// 每个goroutine都做20次自增运算
			for i := 0; i < 20; i++ {
				time.Sleep(time.Millisecond)
				atomic.AddUint32(&cnt, 1)
			}
		}()
	}

	// 等待2s, 等goroutine完成
	time.Sleep(time.Second * 2)
	// 取最终结果
	cntFinal := atomic.LoadUint32(&cnt)

	fmt.Println("cnt:", cntFinal)
}
/*
output:

cnt: 200
*/





你可能感兴趣的:(GoLang)