目录
因为并发的程序需要保证内存可见性,因此了解go语言的happens-before规则很重要,请参考:
后端 - go语言happens-before原则及应用 - 个人文章 - SegmentFault 思否
9.1
package exercisebank1
import (
"log"
)
var deposits = make(chan int)
var balances = make(chan int)
var withdraws = make(chan WithdrawT)
type WithdrawT struct {
amount int
ch chan<- bool
}
func Deposit(amount int) {
deposits <- amount
}
func Withdraw(amount int, ch chan<- bool) {
if cap(ch) < 1 {
log.Print("调用错误,请传入缓存通道")
return
}
withdraws <- WithdrawT{amount, ch}
}
func Balances() int {
return <-balances
}
func teller() {
var balance int
for {
select {
case amount := <-deposits:
balance += amount
case balances <- balance:
case w := <-withdraws:
b := false
if balance >= w.amount {
balance -= w.amount
b = true
}
select { // 使用select防止客户端不取数据阻塞服务端
case w.ch <- b:
default:
}
}
}
}
func init() {
go teller()
}
测试代码:
package main
import (
"fmt"
"sync"
"gopl.io/ch9/exercisebank1"
)
func main() {
f2()
f2()
f2()
}
func f2() {
var wg sync.WaitGroup
for i := 100; i > 0; i-- {
wg.Add(1)
go func() {
exercisebank1.Deposit(100)
wg.Done()
}()
}
success := make(chan bool, 10)
for i := 10; i > 0; i-- {
wg.Add(1)
go func() {
ch := make(chan bool, 1)
exercisebank1.Withdraw(1000, ch)
s := <-ch
success <- s
wg.Done()
}()
}
wg.Wait()
close(success)
totalWithdraw := 0
for s := range success {
if s {
totalWithdraw += 1000
}
}
fmt.Printf("100个协程同时存储100元,10个协程成功取出%d元,账户余额%d\n", totalWithdraw, exercisebank1.Balances())
ch := make(chan bool, 1)
exercisebank1.Withdraw(exercisebank1.Balances(), ch)
<-ch
}
9.2
package main
import (
"fmt"
"sync"
)
var loadOnce sync.Once
var pc [256]byte
func Popcount(x int64) int {
loadOnce.Do(initPc)
return int(pc[byte(x>>(0*8))] + pc[byte(x>>(1*8))] + pc[byte(x>>(2*8))] + pc[byte(x>>(3*8))] + pc[byte(x>>(4*8))] + pc[byte(x>>(5*8))] + pc[byte(x>>(6*8))] + pc[byte(x>>(7*8))])
}
func initPc() {
fmt.Println("init")
for i := range pc {
pc[i] = pc[i/2] + byte(i&1)
}
}
func main() {
fmt.Println(Popcount(1))
fmt.Println(Popcount(255))
}
9.3
Memo代码
因为通过cancle chan来取消http请求的做法已经过时,这里使用context结构来取消http请求
package main
import (
"fmt"
"net/http"
"sync"
)
type Memo struct {
f Func
mu sync.Mutex
cache map[string]*entry
}
type Func func(req *http.Request) (interface{}, error)
type result struct {
value interface{}
err error
}
type entry struct {
v result
ready chan struct{}
cancled chan struct{}
}
func New(f Func) *Memo {
return &Memo{f: f, cache: make(map[string]*entry)}
}
func (memo *Memo) Get(req *http.Request) (interface{}, error) {
key := req.URL.String()
memo.mu.Lock()
res, ok := memo.cache[key]
if !ok {
res = &entry{ready: make(chan struct{}), cancled: make(chan struct{})}
memo.cache[key] = res
memo.mu.Unlock()
res.v.value, res.v.err = memo.f(req)
select {
case <-req.Context().Done():
memo.mu.Lock()
delete(memo.cache, key)
memo.mu.Unlock()
close(res.cancled)
default:
close(res.ready)
}
} else {
memo.mu.Unlock()
select {
case <-res.ready:
case <-req.Context().Done():
return nil, fmt.Errorf("请求被取消")
case <-res.cancled: // 虽然访问相同的url,但因为是不同的请求,上一个请求被取消不该影响该请求继续执行
return memo.Get(req)
}
}
return res.v.value, res.v.err
}
测试函数
package main
import (
"context"
"fmt"
"io/ioutil"
"log"
"net/http"
"sync"
"time"
)
func httpGetBody(req *http.Request) (interface{}, error) {
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return ioutil.ReadAll(resp.Body)
}
func main() {
memo := New(httpGetBody)
incomingURLs := []string{
"http://www.baidu.com",
// "http://www.sina.com",
"http://www.baidu.com",
"http://www.baidu.com",
}
start := time.Now()
sizec := make(chan int, 10)
down := make(chan struct{})
total := 0
go func() {
for s := range sizec {
total += s
}
down <- struct{}{}
}()
var wg sync.WaitGroup
for i, url := range incomingURLs {
i := i
ctx, cancle := context.WithCancel(context.Background())
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
log.Print(err)
continue
}
wg.Add(1)
go func(req *http.Request) {
if i == 2 {
cancle()
}
body, err := memo.Get(req)
if err != nil {
log.Print(err)
} else {
sizec <- len(body.([]byte))
}
wg.Done()
}(req)
}
wg.Wait()
close(sizec)
<-down
fmt.Printf("%s, total size=%d\n", time.Since(start), total)
}
9.4
package main
import (
"fmt"
"runtime"
"time"
)
func main() {
head := make(chan int)
pre := head
var x int64
var m runtime.MemStats
runtime.ReadMemStats(&m)
before := m.Sys
start := time.Now()
for i := 0; i < 300000; i++ {
cur := make(chan int)
xpre := pre
x++
pre = cur
go func() {
v := <-xpre
cur <- v
}()
}
cost1 := time.Since(start)
runtime.ReadMemStats(&m)
fmt.Printf("一共创建了%d个goroutine, 创建耗时%s, 占用内存%fGB\n", x, cost1, float64(m.Sys-before)/1e9)
start = time.Now()
head <- 1
v := <-pre
cost2 := time.Since(start)
fmt.Printf("传递一个元素耗时%s, 元素值%d\n", cost2, v)
}
9.5
package main
import (
"fmt"
"time"
)
func main() {
ping := make(chan string)
pong := make(chan string)
i := 0
// ping 函数
go func() {
for {
<-pong
ping <- "ping"
i++
}
}()
// pong函数
go func() {
for {
<-ping
pong <- "pong"
}
}()
ping <- "ping"
time.Sleep(time.Second)
// 理论上这里的i存在内存可见性问题,需要利用happens-before规则处理,但此处忽略该bug
fmt.Println(i)
}
9.6
package main
import (
"fmt"
"runtime"
"sync"
"time"
)
func main() {
value := int64(1e9)
cpuBound(1, value)
cpuBound(4, value)
cpuBound(8, value)
cpuBound(16, value)
cpuBound(32, value)
}
func cpuBound(threads int, value int64) {
runtime.GOMAXPROCS(threads)
gor := 50
var wg sync.WaitGroup
start := time.Now()
for i := 0; i < gor; i++ {
wg.Add(1)
go func() {
for j := int64(0); j < value/int64(gor); j++ {
}
wg.Done()
}()
}
wg.Wait()
fmt.Printf("线程数%d, 耗时%s\n", threads, time.Since(start))
}