五一空闲把Go的多线程、测试、反射章节的知识快速过了一遍。
初学只要求有个正确的大致理解,打好基础。
下面记录下练习题和代码:
-
请简述线程和协程(goroutine)之间的区别。
协程是从主线程开启的,比线程要轻量。线程的调度由操作系统内核完成,goroutine的调度由Go自身的调用器完成,由程序员在协程的代码中显示调度,不需要进行内核上下文的切换,因此协程效率比线程高。
Go协程占用的内存少,且栈空间的大小可动态伸缩,可以在Go中轻松开启上万个协程。 -
关于channel的特性,下面说法正确的是(A、B、C、D、F)
A. 给一个 nil channel 发送数据,造成永远阻塞
B. 从一个 nil channel 接收数据,造成永远阻塞
C. 给一个已经关闭的 channel 发送数据,引起 panic
D. 从一个已经关闭的 channel 接收数据,如果缓冲区中为空,则返回一个零值
E. 无缓冲的channel和有缓冲的channel都是非同步的
F. 无缓冲的channel是同步的,而有缓冲的channel是非同步的 -
编程实现:使用for循环10次,打印出计数器i的值。打印操作在一个goroutine中实现,main函数通过channel把要打印的数字传递给goroutine。循环结束后,main函数通过另一个channel向goroutine发送停止信号,goroutine收到停止信号后打印“Done”并退出执行。
func exercise2_3() { //构建一个channel传输数字 numChan := make(chan int) //构建一个channel传输停止信号 signalChan := make(chan string) go func() { for i := 0; i < 10; i++ { numChan <- i time.Sleep(time.Second) } close(numChan) signalChan <- "Done" }() for { for i := range numChan { fmt.Print(strconv.Itoa(i) + " ") } fmt.Println() signal := <-signalChan fmt.Println(signal) break } }
-
编程实现:3个协程分别打印A、B、C,即协程1打印A,协程2打印B,协程3打印C,各自打印5遍,要求输出结果必须按ABC的顺序显示,即:ABCABC…
var wg sync.WaitGroup
func exercise2_4() {
// 从A开始,aChan缓存大小为1的Channel
aChan := make(chan int, 1)
bChan := make(chan int)
cChan := make(chan int)
wg.Add(3)
go print(aChan, bChan, 'A')
go print(bChan, cChan, 'B')
go print(cChan, aChan, 'C')
wg.Wait()
fmt.Println()
}
func print(curChan chan int, nextChan chan int, c byte) {
defer wg.Done()
for i := 0; i < 5; i++ {
curChan <- 1
fmt.Print(string(c))
<-nextChan
}
return
}
那么这道题,可谓是很经典了。我曾经写的博文中有这道类似的题,链接在此
那么,这次用Go实现,用到三个channel,利用其互斥访问、阻塞特性,实现了同步操作,好像一条链条。 其实,也可以继续用之前的方法,维持一个互斥量state,利用取余满足条件才操作的方法,实现同步即可,用Go来实现,如下:
对我无比偏执地想要保持前后一致的我感到无语,同样对敢于探索一题多解(多语言多方法)的自己表示鼓励。哈哈哈。
var (
mu sync.Mutex
state int = 0
)
func exercise2_4_2() {
wg.Add(3)
go print2('A', 0)
go print2('B', 1)
go print2('C', 2)
wg.Wait()
fmt.Println()
}
func print2(c byte, stateMode int) {
defer wg.Done()
for i := 0; i < 5; {
mu.Lock()
if state%3 == stateMode {
fmt.Print(string(c))
i++
state++
}
mu.Unlock()
}
}
5.执行以下代码时将打印什么?解决该段代码的问题以确保len(m)打印为10。
package main
import (
“sync”
)
const N = 10
func main() {
m := make(map[int]int)
wg := &sync.WaitGroup{}
mu := &sync.Mutex{}
wg.Add(N)
for i := 0; i < N; i++ {
go func() {
defer wg.Done()
mu.Lock()
m[i] = i
mu.Unlock()
}()
}
wg.Wait()
println(len(m))
}
答:打印1, 10个协程同时运行,读的都是i=0。把for语句放到匿名函数中能保证打印10,修改如下:
go func() {
for i := 0; i < N; i++ {
defer wg.Done()
mu.Lock()
m[i] = i
mu.Unlock()
}
}()
6.实现一个简单的消息队列,支持多生成者、多消费者。
程序说明:PRODUCER_NUM个生产者,CONSUMER_NUM个消费者。每个生产者输入PRODUCE_COUNT个数字,消息数字每次递增1。
var (
msgMu sync.Mutex
msg int = 0
)
var msgQueue = make(chan int, 1)
var finChan = make(chan string, 2)
const PRODUCE_COUNT = 2
const PRODUCER_NUM = 3
const CONSUMER_NUM = 2
func exercise2_6() {
wg.Add(PRODUCER_NUM + CONSUMER_NUM)
//开启多个生产者goroutine
for i := 1; i <= PRODUCER_NUM; i++ {
go producer("producer" + strconv.Itoa(i))
}
//开启多个消费者goroutine
for i := 1; i <= CONSUMER_NUM; i++ {
go consumer("consumer" + strconv.Itoa(i))
}
wg.Wait()
consumer1 := <-finChan
consumer2 := <-finChan
fmt.Printf("%s, %s, All is Done.\n", consumer1, consumer2)
}
func producer(name string) {
defer wg.Done()
for i := 0; i < PRODUCE_COUNT; i++ {
msgMu.Lock()
msgQueue <- msg
fmt.Printf("%s write %d\n", name, msg)
msg++
if msg == PRODUCER_NUM*PRODUCE_COUNT {
close(msgQueue)
}
msgMu.Unlock()
}
}
func consumer(name string) {
defer wg.Done()
for num := range msgQueue {1
// msgMu.Lock()
fmt.Printf("%s read %d\n", name, num)
// msgMu.Unlock()
}
finChan <- name
}
这个等待组WaitGroup太值得说了,Wait()方法,会阻塞主函数,一直等到所有协程都运行结束。
不加这个呢,程序直接结束,协程一个都跑不起来。
7.选取之前练习题中的代码,编写测试代码,执行测试并生成测试覆盖率数据。
func TestWordsGroupByLength(t *testing.T) {
var tests = []struct {
input []string
want int
}{
{nil, 0},
{[]string{}, 0},
{[]string{"ZTE", "Hello", "Zte", "World", "zte", "today", "good", "go", "road", "ok"}, 4},
{[]string{"", " ", " "}, 3},
{[]string{"中国", "China", "AC"}, 3},
}
for _, test := range tests {
if got := wordsGroupByLength(test.input); got != test.want {
t.Errorf("wordsGroupByLength(%q) = %v", test.input, got)
}
}
}
package main
import (
"fmt"
"sort"
)
// func main() {
// strslice := []string{"ZTE", "Hello", "Zte", "World", "zte", "today", "good", "go", "road", "ok"}
// wordsGroupByLength(strslice)
// }
func wordsGroupByLength(strslice []string) int {
if len(strslice) == 0 {
return 0
}
strgroup := make(map[int][]string)
for _, str := range strslice {
strgroup[len(str)] = append(strgroup[len(str)], str)
}
lens := make([]int, 0, len(strgroup))
for length := range strgroup {
lens = append(lens, length)
}
sort.Ints(lens)
for _, len := range lens {
fmt.Printf("Words with length %d : %v\n", len, strgroup[len])
}
return len(strgroup)
}
对于Go自带的测试框架,我只说一句NB!
8.编程实现:List是个切片类型(List := make([]interface{},3),现有以下的数据,要求使用反射筛选出各类型的数据。
List[0] =“123”
List[1] =13579
List[2] =20.2
List[3] =Person{“小明”,18}
type Person struct {
name string
age int
}
List := make([]interface{}, 0, 4)
List = append(List, "123", 13579, 20.2, Person{"小明", 18})
fmt.Println(List)
for _, element := range List {
typeOfEle := reflect.TypeOf(element)
switch typeOfEle.Name() {
case "int":
fmt.Printf("type is %s, kind is %s, value is %d\n",
typeOfEle, typeOfEle.Kind(), reflect.ValueOf(element).Int())
case "float64":
fmt.Printf("type is %s, kind is %s, value is %.1f\n",
typeOfEle, typeOfEle.Kind(), reflect.ValueOf(element).Float())
case "string":
fmt.Printf("type is %s, kind is %s, value is %s\n",
typeOfEle, typeOfEle.Kind(), reflect.ValueOf(element).String())
case "Person":
fmt.Printf("type is %s, kind is %s, value is %v\n",
typeOfEle, typeOfEle.Kind(), reflect.ValueOf(element))
}
}
什么,反射这么酷炫的技能你还不会?!
- 编程实现:每个学生的信息包括学号(num)、姓名(name)、成绩( score),请定义学生信息结构体对象 student,并初始化。然后使用反射原理修改学生基本信息,并输出修改后的结果。
type student struct {
Num int
Name string
Score int
}
//可通过反射修改结构体字段的值需满足条件:可寻址、被导出。
func exercise2_9() {
//初始化
xiaowang := student{}
//获取学生实例的反射值对象地址,然后取出地址的元素
valueOfXiaowang := reflect.ValueOf(&xiaowang).Elem()
//修改学生的学号
//获取Num字段的值
vNum := valueOfXiaowang.FieldByName("Num")
fmt.Printf("修改前的学生学号为: %d\n", vNum)
//修改
vNum.SetInt(12345)
fmt.Printf("修改成功,修改后的学生学号为: %d\n", vNum)
//修改学生的姓名
//获取Name字段的值
vName := valueOfXiaowang.FieldByName("Name")
fmt.Printf("修改前的学生姓名为: %s\n", vName)
//修改
vName.SetString("xiaowang")
fmt.Printf("修改成功,修改后的学生姓名为: %s\n", vName)
//修改学生的成绩
//获取Score字段的值
vScore := valueOfXiaowang.FieldByName("Score")
fmt.Printf("修改前的学生成绩为: %d\n", vScore)
//修改
vScore.SetInt(100)
fmt.Printf("修改成功,修改后的学生成绩为: %d\n", vScore)
}