《go语言圣经》习题答案-第9章

目录

9.1

9.2

9.3

9.4

9.5

9.6


因为并发的程序需要保证内存可见性,因此了解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))
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值