Go学习记录2-20220503

五一空闲把Go的多线程、测试、反射章节的知识快速过了一遍。
初学只要求有个正确的大致理解,打好基础。

下面记录下练习题和代码:

  1. 请简述线程和协程(goroutine)之间的区别。

    协程是从主线程开启的,比线程要轻量。线程的调度由操作系统内核完成,goroutine的调度由Go自身的调用器完成,由程序员在协程的代码中显示调度,不需要进行内核上下文的切换,因此协程效率比线程高。
    Go协程占用的内存少,且栈空间的大小可动态伸缩,可以在Go中轻松开启上万个协程。

  2. 关于channel的特性,下面说法正确的是(A、B、C、D、F)
    A. 给一个 nil channel 发送数据,造成永远阻塞
    B. 从一个 nil channel 接收数据,造成永远阻塞
    C. 给一个已经关闭的 channel 发送数据,引起 panic
    D. 从一个已经关闭的 channel 接收数据,如果缓冲区中为空,则返回一个零值
    E. 无缓冲的channel和有缓冲的channel都是非同步的
    F. 无缓冲的channel是同步的,而有缓冲的channel是非同步的

  3. 编程实现:使用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
    	}
    }
    
  4. 编程实现: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))
		}
	}

什么,反射这么酷炫的技能你还不会?!

  1. 编程实现:每个学生的信息包括学号(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)
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wsws100

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值