Go学习笔记

GO语言中包的使用

  1. 一个目录下的go文件归属一个包,package的声明需要一致
  2. package声明的包和对应的目录名可以不一致,但是我们一般不这么做
  3. 包可以嵌套(和Java一样)
  4. 同包下的函数不需要导包(和Java一样)
  5. main包,main()函数所在的包,其他包不能导入
  6. 导入包的时候,路径要从$GIPATH/src下开始写(就是项目文件夹名称也要写上)

go build的包,生成的.a文件只存在于临时目录
go install的包,会在$GOPATH/pkg/对应平台系统目录里面
另外,如果go build的是main()函数,其依赖的包会在$GOPATH/pkg/对应平台系统目录生成.a文件,其实是因为,编译依赖包用的是go install命令

点操作

点操作的含义就是这个包导入之后在你调用这个包的函数的时候,可以省略前面的包名

package main

import (
	. "fmt"
)

func main() {
	Printf("%v", 100)
}

起别名

可以把报名命名成另一个容易记忆的名字

package main

import (
	f "fmt"
)

func main() {
	f.Printf("%v", 100)
}

_ 操作

如果仅仅需要导入包时执行初始化函数,并不需要包中的其他函数,则可以在导入包的时候匿名导入

import (
	_ "packagename"
)

init 函数

  1. init() 和 main() 时 go 语言中的保留函数,init() 函数用来初始化,在导入一个包的时候如果存在 init() 函数,则会先执行(导包过程中就会执行,而不是使用的时候才执行)
  2. init() 函数无参数无返回值,该函数只能由 go 程序自动调用,无法人为调用
  3. init() 函数可以重复定义,即使是在同一个 go 文件中。导包执行的时候,哪个在上面先执行哪个
  4. 同一个 package 的不同文件,将文件名按照字符串从小到大进行排序,之后按顺序调用各文件中的 init() 函数
  5. 对于不同 package 的文件,按照 import 的先后顺序执行
  6. 如果 package 之间存在依赖关系,调用顺序为最后被依赖的最先被调用
  7. 避免出现循环import
  8. 一个包被其他多个包 import,但只能被初始化一次
package utils

import "fmt"

func init() {
	fmt.Println(1)
}

func init() {
	fmt.Println(2)
}

func init() {
	fmt.Println(3)
}

func Test()  {

}

管理外部包

执行go get github.com/go-sql-driver/mysql,会将源码下载到$GOPATH/src目录下
(也可以自己手动下载,手动复制到$GOPATH/src目录下)

常用包

时间 - time

package main

import (
	"fmt"
	"time"
)

func main() {

	t1 := time.Now()

	t2 := time.Date(2020, 12, 12, 12, 12, 12, 12, time.Local)

	// 时间格式化:必须用"2006-01-02 15:04:05这里的数值(真是不要脸)
	fmt.Println(t1.Format("2006-01-02 15:04:05"))
	fmt.Println(t2.Format("2006-01-02 15:04:05"))

	// 字符串转时间
	if t3, err := time.Parse("2006-01-02 15:04:05", "1999-12-12 00:00:00"); err == nil {
		fmt.Println(t3)
	} else {
		fmt.Println(err)
	}

	// 常用方法
	fmt.Println(t1.Date())
	fmt.Println(t1.Clock())
	fmt.Println(t1.Month())
	fmt.Println(t1.Hour())
	fmt.Println(t1.Second())
	fmt.Println(t1.Day())
	fmt.Println(t1.Year())
	fmt.Println(t1.Unix()) // 时间戳,秒
	fmt.Println(t1.UnixNano()) // 时间戳,纳秒

	// 通过Add方法可以获取指定时间段之后(或者前,用负号)的时间
	t4 := t1.Add(time.Second * 60)
	fmt.Println(t4)

	// 以后到了channel再看有什么用
	t5 := t1.After(t1)
	fmt.Println(t5)

	time.Sleep(time.Second * 10)
}

文件 - file、io、os

基本操作

package main

import (
	"fmt"
	"os"
	"path/filepath"
)

func main() {

	// 获取文件信息
	fileInfo, err := os.Stat("Main.go")
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println("文件名称:", fileInfo.Name())
		fmt.Println("文件大小:", fileInfo.Size())
		fmt.Println("文件权限:", fileInfo.Mode())
		fmt.Println("修改时间:", fileInfo.ModTime().Format("2006-01-02 15:04:05"))
	}

	// 判断是否为绝对路径
	fmt.Println(filepath.IsAbs("D:\GOPATH\src"))
	fmt.Println(filepath.IsAbs("Main.go"))

	// 获取绝对路径
	fmt.Println(filepath.Abs("Main.go"))

	// 目录拼接(这里可以用..来获取上级目录)
	fmt.Println(filepath.Join("D:\GOPATH\src", ".."))
	fmt.Println(filepath.Join("D:\GOPATH\src", "go-test"))

	// 创建目录
	fmt.Println(os.Mkdir("test", os.ModePerm))
	fmt.Println(os.MkdirAll("D:\test", os.ModePerm)) // 创建完整路径

	// 创建文件:如果文件不存在就会创建,如果文件已存在就会截断文件(把文件清空)
	file_1, _ := os.Create("test.txt") // 创建文件默认权限是0666,可读可写不可执行
	fmt.Println(file_1)

	// 打开文件:Open函数打开文件为只读模式
	file_2, _ := os.Open("test.txt")
	fmt.Println(file_2)

	// 已指定模式打开文件(第三个参数是如果不存在,创建一个文件时,指定其权限)
	file_3, _ := os.OpenFile("test.txt", os.O_RDWR, os.ModePerm)
	fmt.Println(file_3)

	// 关闭文件
	fmt.Println(file_1.Close())
	fmt.Println(file_2.Close())
	fmt.Println(file_3.Close())

	// Remove函数只能删除一级:如果有嵌套需要先删除最底层的文件或文件夹
	// RemoveAll可以直接删除任意路径
	// 删除文件
	fmt.Println(os.Remove("test.txt"))
	//fmt.Println(os.RemoveAll("全路径"))

	// 删除目录
	fmt.Println(os.Remove("test"))
	//fmt.Println(os.RemoveAll("全路径"))

}

io基本操作

package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"
)

func main() {

	testIOWriter("test.txt")
	testIOReader("test.txt")
	testCopyFile("E:\Pictures\15030Q05046-1.jpg", "test_1.jpg")
	testIOCopy("E:\Pictures\15030Q05046-1.jpg", "test_2.jpg")
	testIOUtilCopy("E:\Pictures\15030Q05046-1.jpg", "test_3.jpg")
	testSeek("test.txt")
}

func testIOReader(file string) {
	file_1, _ := os.OpenFile(file, os.O_RDONLY, os.ModePerm)
	defer file_1.Close()

	bs_1 := make([]byte, 10, 10)

	for {
		length_1, err := file_1.Read(bs_1)
		if length_1 == 0 || err == io.EOF {
			fmt.Println("
文件已读取完毕~")
			break
		}
		fmt.Print(string(bs_1[:length_1]))
	}
}

func testIOWriter(file string) {

	file_1, _ := os.OpenFile(file, os.O_CREATE|os.O_APPEND, os.ModePerm)
	defer file_1.Close()

	_, _ = file_1.Write([]byte("hello go~
"))
	_, _ = file_1.WriteString("test test ~
")

	fmt.Println("文件写入完毕:", file_1)
}

// 通过io实现文件复制
func testCopyFile(src, dst string) {

	os.RemoveAll(dst)

	fileInfo, _ := os.Stat(src)

	srcFile, _ := os.Open(src)
	defer srcFile.Close()

	dstFile, _ := os.OpenFile(dst, os.O_CREATE|os.O_APPEND, os.ModePerm)
	defer dstFile.Close()

	bs := make([]byte, 1024*10, 1024*10)

	// 测试一下图片复制一半的效果
	total := 0

	for {
		length, err := srcFile.Read(bs)
		total += length
		if total >= int(fileInfo.Size()/2) || length == 0 || err == io.EOF {
			fmt.Println("
文件已复制完毕")
			break
		}

		dstFile.Write(bs[:length])
	}
}

func testIOCopy(src, dst string) {

	os.RemoveAll(dst)

	srcFile, _ := os.Open(src)
	defer srcFile.Close()

	dstFile, _ := os.OpenFile(dst, os.O_CREATE|os.O_APPEND, os.ModePerm)
	defer dstFile.Close()

	io.Copy(dstFile, srcFile)
	io.CopyBuffer(dstFile, srcFile, make([]byte, 1024*100, 1024*100))
}

// ioutil包,封装了一系列简单的io操作,一般用来操作小文件比较合适,不适合大的文件操作,因为它是一次操作的,容易出现内存溢出
func testIOUtilCopy(src, dst string) {

	os.RemoveAll(dst)

	bs, err := ioutil.ReadFile(src)
	if err != nil {
		fmt.Println("文件读取异常:", err)
	} else {
		ioutil.WriteFile(dst, bs, os.FileMode(0666))
		fmt.Println("文件复制完毕")
	}

}

// 通过Seek接口可以实现断点续传(没看出来有什么卵用)
func testSeek(file string) {
	/*
		Seek(offset int64, whence int) (int64, error)
		SeekStart   = 0 // seek relative to the origin of the file
		SeekCurrent = 1 // seek relative to the current offset
		SeekEnd     = 2 // seek relative to the end
	*/
	file_1, _ := os.Open(file)
	defer file_1.Close()

	// 光标当前位置向后偏移,其后执行read或者write方法
	file_1.Seek(4, io.SeekCurrent)

	bs := make([]byte, 1024, 1024)
	length, _ := file_1.Read(bs)
	fmt.Println(string(bs[:length]))
}

bufio

package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
)

func main() {
	testBufIOWriter("test.txt")
	testBufIOReader("Main.go")
}

func testBufIOReader(str string) {

	file, _ := os.Open(str)
	defer file.Close()

	reader := bufio.NewReader(file)

	//line, _, _ := reader.ReadLine() // 不推荐使用,不一定可以读取完整的一行
	line, err := reader.ReadString('
') // 推荐使用这种方法读取整行
	if err == io.EOF {
		fmt.Println("文件已读取完成")
	} else {
		fmt.Println(line)
	}

	// 结合标准io输入流,获取控制台输入
	stdReader := bufio.NewReader(os.Stdin)
	fmt.Println(stdReader.ReadString('
'))
}

func testBufIOWriter(str string) {

	file, _ := os.OpenFile(str, os.O_CREATE|os.O_APPEND, os.ModePerm)
	defer file.Close()

	writer := bufio.NewWriter(file)

	writer.WriteString("testBufIOWriter hello go ~
")
	writer.Flush() // 必须要刷新才会真正写入磁盘,否则可能写不进去(只有缓冲区满了才会自己写一次)
}

iouitl

package main

import (
	"fmt"
	"io/ioutil"
	"os"
)

func main() {

	bs, _ := ioutil.ReadFile("Main.go")
	fmt.Println(string(bs)) // 一次读完,不适合大文件读取

	ioutil.WriteFile("test.txt", bs, os.FileMode(0666)) // 注意:会清空原文件

	fileInfos, _ := ioutil.ReadDir("D:\")
	for _, info := range fileInfos {
		fmt.Println(info.Name(), info.IsDir())
	}

	// 第一个参数是一个目录,必须存在,第二个参数是生成的临时文件夹前缀
	// 生成的临时目录需要手动去删除
	tempDir, _ := ioutil.TempDir("utils", "ttt-")
	defer func() {
		err := os.Remove(tempDir)
		if err != nil {
			fmt.Println("删除临时目录失败:", err)
		}
	}()
	fmt.Println(tempDir)

	tempFile, _ := ioutil.TempFile("utils", "fff-")
	defer func() {
		tempFile.Close()
		err := os.Remove(tempFile.Name())
		if err != nil {
			fmt.Println("删除历史文件失败:", err)
		}
	}()
	fmt.Println(tempFile.Name())
}

并发

初始 goroutine

package main

import (
	"fmt"
	"time"
)

func main() {

	/*
	go中的协程(goroutine),只需要用关键字go即可

	1. go关键字执行的函数,其返回值会被忽略,所以最好就不要有返回值
	2. main的goroutine执行结束之后,其他的goroutine也会结束

	主goroutine的作用:
	0. 设定每个goroutine所能申请的栈空间的最大尺寸(32位机器是250M,64位机器是1G)。如果有某个goroutine的栈空间尺寸大于这个限制,系统就会引发一个栈溢出(stack overflow)运行时panic,随后结束程序
	1. 创建一个特殊的defer语句,用于主goroutine结束之后进行必要的善后处理。(因为主goroutine也可能非正常的结束)
	2. 启动专门用于在后台清扫内存垃圾的goroutine,并设置GC可用标识
	3. 执行main包中的init函数
	4. 执行main函数
	执行main函数后,它还会检查主goroutine是否引发了运行时panic,并进行必要的处理,最后主goroutine会结束自己以及当前进程的运行
	 */

	for i := 0; i < 10000; i++ {
		go test_1()
		go test_2()
	}

	time.Sleep(time.Second * 30)
}

func test_1() {
	for {
		fmt.Println("test_1")
		time.Sleep(time.Second)
	}
}

func test_2() {
	for {
		fmt.Println("	test_2")
		time.Sleep(time.Second)
	}
}

goroutine 并发模型

GO语言并发模型中的四个重要结构,分别是M、G、P、Sched,前三个定义在runtime.h,Sched定义在proc.h

  • Sched结构就是调度器,它为维护有存储M和G的队列以及调度器的一些状态信息
  • M结构是Machine,系统线程,它有操作系统管理的,goroutine就是运行在M之上的。M是一个很大的结构,里面维护小对象内存caceh(mcache)、当前执行的goroutine、随机数发生器等非常多的信息
  • P结构是Processos,处理器,它的主要用途是用来执行goroutine的,它维护了一个goroutine队列,即runqueue。Processos是让我们从N:1调度到M:N调度的重要部分
  • G是goroutine实现的核心结构,它包含了栈、指令指针、以及其他对调度goroutine很重要的信息,例如其阻塞的channel
    Processor的数量在启动时被设置为环境变量GOMAXPROCS的值,或者通过运行时调用函数GOMAXPROCS()进行设置。Processor数量固定意味着任意时刻只有GOMAXPROCS个线程在运行go代码

在单核处理器的场景下,所有goroutine运行在同一个M系统线程中,每一个M系统线程维护一个Processor,任何时刻,一个Processor中只有一个goroutine,其他goroutine在runqueue中等待。一个goroutine运行完自己的时间片后,让出上下文,回到runqueue中。 多核处理器的场景下,为了运行goroutines,每个M系统线程会持有一个Processor。

当正在运行的goroutine阻塞的时候,例如进行系统调用,会再创建一个系统线程(M1),当前的M线程放弃了它的Processor,P转到新的线程中去运行。

当其中一个Processor的runqueue为空,没有goroutine可以调度。它会从另外一个上下文偷取一半的goroutine。

runtime

package main

import (
	"fmt"
	"runtime"
)

func init() {
	// GO的1.8版本之前可以设置一下,1.8版本之后不需要设置了
        // 因为:如果你没有特意配置runtime.SetMaxThreads,那么在可没有复用的线程下会一直创建新线程。 golang默认的最大线程数10000个线程,这个是硬编码。 如果想要控制golang的pthread线程数可以使用 runtime.SetMaxThreads() 。
	// 如果要设置Processor数量,最好时在main包的init函数里面
	runtime.GOMAXPROCS(runtime.NumCPU())
}

func main() {

	fmt.Println(runtime.GOROOT())
	fmt.Println(runtime.GOOS)
	fmt.Println(runtime.NumCPU())

	// 当前goroutine让出CPU时间片,类似Java中的Thread.yield();
	runtime.Gosched()

	// 终止当前的goroutine(defer还是会执行的)
	runtime.Goexit()
}

GO语言不推荐使用锁

package main

import (
	"fmt"
	"strconv"
	"sync"
)

func main() {

	/*
		go语言中的并发问题

		go语言也支持通过上锁的方式,某一段时间只允许一个goroutine来访问这个共享数据,当前goroutine访问完毕解锁后,其他goroutine才能访问

		需要借助sync包下的锁操作

		但是不建议使用:不要以共享内存的方式通信,而要以通信的方式去共享内存
	*/

	testWaitGroup() // 同步等待组
	testMutex() // 互斥锁
	testRWMutex() // 读写锁
}

// 同步等待组,类似Java中的CountDownLatch
func testWaitGroup() {

	//var wg sync.WaitGroup // 这个写法也行
	wg := sync.WaitGroup{} // WaitGroup的成员都不需要赋值,直接用零值即可
	wg.Add(10)

	for i := 0; i < 10; i++ {
		go func(s string) {
			fmt.Println(s)
			wg.Done()
		}(strconv.Itoa(i))
	}

	wg.Wait()
	fmt.Println("等待完毕")
}

// 互斥锁,类似synchronized
func testMutex()  {

	wg := sync.WaitGroup{}
	wg.Add(10)

	mx := sync.Mutex{}

	var count int // go语言中并不需要初始化,带有默认0值

	for i := 0; i < 10; i++ {
		go func(s string) {
			for j := 0; j < 10000; j++ {
				mx.Lock()
				count++
				mx.Unlock()
			}
			wg.Done()
		}(strconv.Itoa(i))
	}

	wg.Wait()
	fmt.Println(count) // 不加锁的话这里的值就会混乱
}

// 读写锁
func testRWMutex()  {

	/*
	RWMutex时基于Mutex实现的

	读锁可以重复获取
	但是写锁一旦获取,释放前既不能获取读锁也不能获取写锁
	读锁释放前无法获取写锁?
	 */

	wg := sync.WaitGroup{}
	wg.Add(10)

	rx := sync.RWMutex{}

	for i := 0; i < 10; i++ {
		go func(s string) {
			for j := 0; j < 10; j++ {
				rx.RLock() // 读锁
				fmt.Println("读锁获取", s)
				rx.RUnlock() // 读解锁
				fmt.Println("读锁释放", s)
				rx.Lock()    // 写锁
				fmt.Println("写锁获取", s)
				rx.Unlock() // 写解锁
				fmt.Println("写锁释放", s)
			}
			wg.Done()
		}(strconv.Itoa(i))
	}

	wg.Wait()
}

channel 通道

通道的注意点:

  1. 用于goroutine,传递消息的
  2. 通道必须有关联的数据类型,nil chan是没有意义的
  3. 阻塞(chan <- data,向通道写数据,是阻塞的,直到另一个goroutine中读取了数据才会解除阻塞;data <- chan,从通道读数据,也是阻塞的,直到另一个goroutine中写入了数据才会解除阻塞)
  4. 本身channel就是同步的,意味着同一时间只有一条goroutine来操作
  5. 通道时goroutine之间的连接,所以通道的发送和接受必须在不同的goroutine中
    (基本就是要先开始读再开始发)

初识通道

package main

import (
	"fmt"
)

func main() {
	/*
	    通道用于goroutine之间通信,每个通道都有与其相关的类型(该通道允许传输的数据类型)

		声明通道:var 变量名称 chan 数据类型
		创建通道:变量名称 = make(chan 数据类型)

	     通道是引用数据类型的,数据传递时引用传递
	*/

	chan_1 := make(chan string)


	go func() {
		// 向通道写入数据
		chan_1 <- "你好呀"
	}()

	// 从通道读取数据,是阻塞式的
	data := <- chan_1
	fmt.Println(data)
}

关闭通道和通道上范围循环

package main

import "fmt"

func main() {

	/*
	关闭通道和通道上范围循环
	 */

	ch := make(chan int)

	go func() {
		for i := 0; i < 10000; i++ {
			ch <- i // 每写一个就会阻塞一次,直到其他goroutine读取了这个数据才会解除阻塞
		}
		// 发送者可以通过关闭通道,来通知接收方不会有更多的数据被发送到channel上
		close(ch)
	}()

	// 方式一:
	//for {
	//	data, ok := <- ch // ok位false表示尝试同一个已经关闭的通道获取数据,ok位true表示成功从通道获取到了一个数据
	//	if ok {
	//		fmt.Println(data)
	//	} else {
	//		fmt.Println("发送完毕")
	//		break
	//	}
	//}

	// 方式二:也可以使用for - range简化写法
	for i := range ch {
		fmt.Println(i)
	}
	fmt.Println("发送完毕")

}

缓冲通道

package main

import "fmt"

func main() {

	/*
	非缓冲通道:make(chan T)
		一次发送一次接收,都是阻塞的

	缓冲通道:make(chan T, capacity)
		发送:缓冲区数据满了才会阻塞
		接收:缓冲区数据空了才会阻塞
	 */

	chbuf := make(chan int, 1024)
	fmt.Println(len(chbuf), cap(chbuf))

	go func() {
		for i := 0; i < 10000; i++ {
			chbuf <- i
		}
		close(chbuf)
	}()

	for i := range chbuf {
		fmt.Println(len(chbuf), cap(chbuf), i)
	}
}

定向通道

package main

import "fmt"

func main() {

	/*
		定向通道(单项通道)
		之前的demo都用的双向通道,既可以读又可以写

		ch1 := make(chan <- int) // 只能写不能读
		ch2 := make(<- chan int) // 只能读不能写

		定向通道一般作为参数,用来限制函数中的某些操作(比如在某个函数中只能写或者只能读),实际传入的参数往往还是双向通道(单向通道形参可以接收双向通道实参)
	*/

	ch := make(chan int, 1024)

	go func() {
		for i := 0; i < 10000; i++ {
			testIn(ch, i)
		}
		close(ch)
	}()

	testOut(ch)
}

func testIn(ch chan<- int, i int) {
	ch <- i
}

func testOut(ch <-chan int) {
	for i := range ch {
		fmt.Println(i)
	}
}

time包中的通道相关函数 - 定时器

package main

import (
	"fmt"
	"time"
)

func main() {

	/*
		time包中的通道相关函数 - 定时器

		定时器:标准库中的Timer让用户可以定义自己的超时逻辑,尤其是在应对select处理多个channel的超时、单channel读写超时等情形时尤为方便

		Timer是一次性的时间触发事件,与Ticker不同,Ticker是按照一定时间间隔持续触发时间事件

		t1 := time.NewTimer(d)
		t2 := time.AfterFunc(d, f)
		t3 := time.After(d)

		Timer的三要素:
			定时时间 d
			触发动作 f
			事件channel t.C
	*/

	//test_1() // time.NewTimer
	//test_2() // time.After
	test_3() // time.AfterFunc
}

func test_1() {
	t1 := time.NewTimer(3 * time.Second)
	fmt.Printf("t1: %T
", t1)

	go func() {
		fmt.Println(time.Now())
		// 这里会阻塞三秒,通道中的数据是3s之后时间
		fmt.Println(<-t1.C)
	}()

	time.Sleep(4 * time.Second)
	flag := t1.Stop() // 计时器可以提前停止(如果计时器已经停止,则返回false,如果计时器尚未停止则返回true并停止计时器)
	if flag {
		fmt.Println(111)
	} else {
		fmt.Println(222)
	}
}

func test_2() {
	t2 := time.After(3 * time.Second) // 相当于t1.C
	fmt.Printf("t2: %T
", t2)

	go func() {
		fmt.Println(time.Now())
		// 这里会阻塞三秒,通道中的数据是3s之后时间
		fmt.Println(<-t2)
	}()

	time.Sleep(4 * time.Second)
}

func test_3() {
	t3 := time.AfterFunc(3 * time.Second, func() {
		fmt.Println("定时器t3结束", time.Now())
	})
	fmt.Printf("t3: %T
", t3)

	go func() {
		fmt.Println(time.Now())
		// 这里会阻塞三秒,然后执行上面指定的函数
		<-t3.C
		// 这里似乎执行不到,不知道为啥
		fmt.Println("~~~~~~~~~~")
	}()

	time.Sleep(4 * time.Second)
	flag := t3.Stop() // 计时器可以提前停止(如果计时器已经停止,则返回false,如果计时器尚未停止则返回true并停止计时器)
	if flag {
		fmt.Println(111)
	} else {
		fmt.Println(222)
	}
}

select 语句

说明:

  1. 语法结构上有点像switch...case...(仅仅是语法结构)
  2. 每个case都必须是一个通信
  3. 所有被发送的表达式都会被求值
  4. 如果有多个case可以运行,select会随机公平的选择一个来执行,其他的不会执行
  5. 否则,如果有default语句,则执行,没有的话select将会阻塞,直到某个case可执行
  6. Go不会重新对channel的值进行求值
package main

import (
	"fmt"
	"time"
)

func main() {

	ch1 := make(chan string)
	ch2 := make(chan string)

	go func() {
		for {
			ch1 <- "通道1"
			time.Sleep(3 * time.Second)
		}
	}()

	go func() {
		for {
			ch2 <- "通道2"
			time.Sleep(2 * time.Second)
		}
	}()

	for {
		select {
		case data := <-ch1:
			fmt.Println(data)
		case data := <-ch2:
			fmt.Println(data)
		default:
			fmt.Println("没有新数据")
		}
		time.Sleep(1 * time.Second)
	}
}

反射

初识反射

package main

import (
	"fmt"
	"reflect"
)

func main() {

	/*
		反射:

		在Go中,每个interface变量都有一个pair,pair记录了实际变量的值和类型:(value, type)

		TypeOf - 用来动态获取输入参数值的类型
		ValueOf - 用来动态获取输入参数的值

		反射的缺点:
		1. 可读性差
		2. 编译过程难以发现错误
		3. 对性能会产生比较大的影响,往往会满一到两个数量级
	*/

	p := Persion{name: "zhangsan"}

	v := reflect.ValueOf(p)
	t := reflect.TypeOf(p)

        // Kind是类别,如stauct,Type是具体的类型
	fmt.Println(v.Kind(), v)
	fmt.Println(t.Kind(), t)

	// 空接口类型可以被任意赋值,类似与动态语言
	var i interface{}
	i = 1
	i = "hello go"
	i = Persion{name: "lisi"}
	i.(Persion).show()
}

type Persion struct {
	name string
}

func (p Persion) show() {
	fmt.Printf("my name is %v
", p.name)
}

反射与结构体

package main

import (
	"fmt"
	"reflect"
)

func main() {

	/*
		反射三大定律:
		1. 反射可以通过接口值得到反射对象
		2. 反射可以从反射对象获得接口值
		3. 如果需要操作一个反射变量,则其值必须可以修改
	*/
	test(Persion{name: "zhangsan"})
}

func test(i interface{}) {

	t := reflect.TypeOf(i)
	v := reflect.ValueOf(i)
	fmt.Println(v)

	if t.Kind() == reflect.Struct {

		fmt.Println("遍历成员:", t.NumField())
		for ii := 0; ii < t.NumField(); ii++ {
			fmt.Println(t.Field(ii))
		}

		fmt.Println("遍历方法:", t.NumMethod())
		for ii := 0; ii < t.NumMethod(); ii++ {
			fmt.Println(t.Method(0))
		}

	}
}

type Persion struct {
	name string // 反射可以获取私有的成员,但无法获取私有的方法
}

func (p Persion) show_1() {
	fmt.Printf("my name is %v
", p.name)
}

// 注意:反射只能获取公开的方法,无法获取私有的方法
func (p Persion) Show_2() {
	fmt.Printf("my name is %v
", p.name)
}

反射 - 修改实际值

package main

import (
	"fmt"
	"reflect"
)

func main() {

	p := Persion{Name: "zhangsan"}

	v := reflect.ValueOf(&p)
	elem := v.Elem()        // 只有上面传入指针,这里才能正常执行,否则将出现panic
	canSet := elem.CanSet() // 判断是否可以重新赋值

	if canSet {
		elem.FieldByName("Name").SetString("lisi") // 注意:只能修改公共的属性,无法修改私有的属性
	}

	p.Show_2()
}

type Persion struct {
	Name string // 反射可以获取私有的成员,但无法获取私有的方法
}

func (p Persion) show_1() {
	fmt.Printf("my name is %v
", p.Name)
}

// 注意:反射只能获取公开的方法,无法获取私有的方法
func (p Persion) Show_2() {
	fmt.Printf("my name is %v
", p.Name)
}

反射 - 调用方法和函数

package main

import (
	"fmt"
	"reflect"
)

func main() {

	// 方法的反射
	p := Persion{Name: "zhangsan"}

	v := reflect.ValueOf(&p)

	v.MethodByName("Show_1").Call(nil)                                     // Call的参数是一个切片,如果实际函数没有值,传nil或者是空切片都行
	v.MethodByName("Show_2").Call([]reflect.Value{reflect.ValueOf("aaa")}) // 创建一个切片作为函数参数,注意参数类型是reflect.Value

	// 函数的反射
	t := reflect.ValueOf(test)
	if t.Kind() == reflect.Func {
		input := make([]reflect.Value, 1, 1) // 使用make创建的切片没法直接赋值,不太方便
		input[0] = reflect.ValueOf("go")
		t.Call(input)
	}

	// Call的返回值是 []reflect.Value ,通过反射执行的函数或方法的返回值保存在这个切片中,返回有几个这里的长度就是多少
}

type Persion struct {
	Name string // 反射可以获取私有的成员,但无法获取私有的方法
}

func (p Persion) Show_1() {
	fmt.Printf("my name is %v
", p.Name)
}

// 注意:反射只能获取公开的方法,无法获取私有的方法
func (p Persion) Show_2(message string) {
	fmt.Printf("my name is %v - %v
", p.Name, message)
}

func test(msg string) {
	fmt.Println("hello", msg)
}

原文地址:https://www.cnblogs.com/CSunShine/p/13179386.html