Golang笔记_day02

接上篇Channel案例

func GoroutineChannel() {
	ch1 := make(chan int)
	ch2 := make(chan int)
	go func() {
		for i := 0; i < 10; i++ {
			ch1 <- i
			time.Sleep(time.Second)
		}
	}()

	go func() {
		for j := 0; j < 10; j++ {
			ch2 <- j
			time.Sleep(time.Second)
		}
	}()

	for i := 0; i < 20; i++ {
		select {
		case data := <-ch1:
			fmt.Println("data from ch1:", data)
		case data := <-ch2:
			fmt.Println("data from ch2:", data)
		default: //使用 default 实现非阻塞读写,不要default流程就会阻塞一直监听channel数据发送
			fmt.Println("no data ready")
			time.Sleep(time.Second)
		}
	}
	fmt.Println("finish")
}

/*
GoroutineChannel的改进版,将循环取channel的值改为无限循环,且监听发送数据是否完成,
若完成,则主动退出,然后加了超时退出的判断,防止一直阻塞
*/
func GoroutineChannel2() {
	ch1 := make(chan int)
	ch2 := make(chan int)
	chstop := make(chan bool)
	wg.Add(2)
	go func() {
		for i := 0; i < 10; i++ {
			ch1 <- i
			time.Sleep(time.Second)
		}
		wg.Done()
	}()

	go func() {
		for j := 0; j < 10; j++ {
			ch2 <- j
			time.Sleep(time.Second)
		}
		wg.Done()
	}()

	go func() {
		for {
			select {
			case data := <-ch1:
				fmt.Println("data from ch1:", data)
			case data := <-ch2:
				fmt.Println("data from ch2:", data)
			case <-chstop:
				fmt.Println("data send ok !!")
				return
			case <-time.After(30 * time.Second):
				fmt.Println("timeout !!")
				return
			default: //使用 default 实现非阻塞读写,不要default,流程就会阻塞一直监听channel数据发送
				fmt.Println("no data ready")
				time.Sleep(time.Second)
			}
		}
	}()
	wg.Wait()
	chstop <- true
	fmt.Println("finish")
}

/*
GoroutineChannel2的改进,将chstop改为用Context管理协程的
*/
func GoroutineChannel3() {
	ch1 := make(chan int)
	ch2 := make(chan int)
	ctx, cancel := context.WithCancel(context.Background())
	wg.Add(2)
	go func() {
		for i := 0; i < 10; i++ {
			ch1 <- i
			time.Sleep(time.Second)
		}
		wg.Done()
	}()

	go func() {
		for j := 0; j < 10; j++ {
			ch2 <- j
			time.Sleep(time.Second)
		}
		wg.Done()
	}()

	go func(ctx context.Context) {
		for {
			select {
			case data := <-ch1:
				fmt.Println("data from ch1:", data)
			case data := <-ch2:
				fmt.Println("data from ch2:", data)
			case <-ctx.Done():
				fmt.Println("data send ok !!")
				return
			case <-time.After(30 * time.Second):
				fmt.Println("timeout !!")
				return
			default: //使用 default 实现非阻塞读写,不要default,流程就会阻塞一直监听channel数据发送
				fmt.Println("no data ready")
				time.Sleep(time.Second)
			}
		}
	}(ctx)
	wg.Wait()
	cancel()
	fmt.Println("finish")
}

一.闭包

1. 概念

     闭包是指一个函数(或函数值)与其引用的外部变量(自由变量)的绑定关系。换句话说,闭包是一个函数和其相关的引用环境的组合体。

      在闭包中,内部函数可以访问外部函数的变量,即使外部函数已经执行完毕,内部函数仍然可以使用外部函数的变量。这是因为闭包会在创建时将外部变量的引用保存在自己的环境中,所以即使外部函数退出后,这些变量仍然可以被内部函数访问和操作

  (1).闭包是一个结构体,里面存储了一个函数和一个关联的环境;

  (2).环境里包括函数内部的局部变量,也包括函数外部定义但在函数内部引用的自由变量;

  (3).对于值的处理可以是值拷贝,也可以是引用;

2. 闭包的使用场景

使用闭包的意义是什么?主要就是缩小变量作用域,减少对全局变量的污染

     (1) 延迟执行:闭包可以用于实现延迟执行的功能。通过在函数内部定义一个闭包,并将其作为参数传递给其他函数或方法,在需要执行的时候再调用闭包。这在处理资源释放、错误处理等情况下非常有用。

     (2) 记忆化:闭包可以用于实现记忆化功能,即缓存函数的计算结果,以避免重复计算。通过在闭包中定义一个缓存,并在每次调用闭包时检查缓存中是否存在计算结果,可以提高程序的性能。

     (3) 函数工厂:闭包可以用于生成函数。通过在一个函数中定义并返回一个闭包,可以根据不同的参数生成不同的函数。这在创建具有不同配置或上下文的函数时非常有用。

     (4) 事件回调:闭包可以用作事件回调函数。当某个事件发生时,闭包可以被调用,并可以访问其所在环境中的变量和状态。

     (5) 并发编程:闭包可以在并发编程中用于共享变量和状态的安全访问。通过将闭包传递给goroutine,在闭包中访问共享变量时可以使用互斥锁等机制来保证线程安全。

      这些只是闭包的一些常见应用场景,实际上闭包在编程中非常灵活,可以根据具体的需求进行创造性的应用。通过使用闭包,可以更好地组织和管理代码,实现更灵活、可复用和可扩展的功能。

3.闭包问题

     (1).延迟绑定

       闭包定义的时候并不是真正的在执行,只有当我们调用的时候才真正的执行,每次执行的时候他都会去找到他引用环境的最新值

     (2).逃逸分析

        逃逸分析是Go编译器的一个优化技术,用于确定一个局部变量是否逃逸到了堆上分配内存。如果一个变量逃逸到了堆上,意味着它在函数执行完后仍然可以被访问,编译器会将其分配在堆上,以保证其生命周期。       
      闭包中的函数常常会引用外部的变量,这些变量可能是局部变量或函数的参数。如果闭包中的函数将这些外部变量持久化(返回函数、存储到全局变量等),那么这些变量就逃逸到了堆上,因为它们在函数执行完后仍然可以被访问。逃逸分析会判断这些变量是否逃逸,并根据情况将其分配在堆上。

        逃逸分析的结果会对代码的性能和内存分配产生影响。如果闭包中的变量逃逸到了堆上,可能会导致额外的内存分配和垃圾回收的开销。因此,在设计闭包时,需要注意避免不必要的变量逃逸,尽量减少对堆的依赖,以提高代码的性能和效率。

4.代码案例

(1)延迟绑定问题

func foo(x int) []func() {
	var fs []func()
	values := []int{1, 2, 3, 5}
	for _, val := range values {
		fs = append(fs, func() {
			fmt.Printf("foo val = %d\n", x+val)
		})
	}
	return fs
}
func Closure1() {
	for _, f := range foo(11) { //注意此处的用法,遍历的不是函数,是函数的返回值,可以借鉴,但是注意函数的异常处理,
		f() //结果输出的都是16,我们期望输出的是12,13,14,16 。这个坑我们在工作中也会经常碰到
		/*
			原因:
			定义闭包 - 这个时候闭包只是定义,并没有使用外部的值
			执行,执行的时候闭包回去寻找最新的环境引用值,有以上go for 循环底层实现分析可知,我们实际上使用的是值 v2,
			v2每次都会重新覆盖。所以当闭包引用环境最新的值时,实际访问的都是v2,所以最终访问到的 val 都是5
		*/
	}

	for i := 0; i < 10; i++ {
		go func() {
			fmt.Println("i_1:", i) //我电脑上的输出结果:10 10 10 10 10 5 9 10 10,实际上每次执行输出结果都不一样
		}()
		/*原因分析:
			定义闭包
		    异步执行闭包
		    异步执行的过程寻找最新的 v2,由于是并发执行的,所以多个协程很可能访问到同一个 v2
				ha := a
				hv1 := 0
				hn := len(ha)
				v1 := hv1
				v2 := nil
				for ; hv1 < hn; hv1++ {
				    tmp := ha[hv1]
				    v1, v2 = hv1, tmp
				    ...
				}
			go 底层每次都会把值赋给 temp, 然后把 temp 赋值给 v2,实际上我们访问的是 v2 变量
		*/
	}

	for i := 0; i < 10; i++ {
		go func() {
			fmt.Println("i_2:", i) //输出结果:1 2 3 4 5 6 7 8 9,为什么延时一秒输出就正常了
		}()
		time.Sleep(1 * time.Second)
		/*
			执行过程和例子二一致,但是为什么加一秒延时就正常了,延时主要是导致协程不在并发执行了,所以每个协程获取到的都是当前引用环境的 v2
		*/
	}

	/*
	   为什么以上例子会出现这些结果
	   1 延时绑定  闭包定义的时候并不是真正的在执行,只有当我们调用的时候才真正的执行,如例子一。每次执行的时候他都会去找到他引用环境的最新值;
	   2 Go 语言遍历数组和切片时会复用变量
	*/

	//如何解决延时绑定带来的问题
	//值覆盖
	for i := 0; i < 10; i++ {
		i := i
		go func() {
			fmt.Println("i_3", i)
		}()
	}
	//值传递
	for i := 0; i < 10; i++ {
		go func(i int) {
			fmt.Println("i_4", i)
		}(i)
	}

	//根据值覆盖的例子 foo1 改造后可以正常输出 12,13,14,16
	for _, f1 := range foo1(11) {
		f1()
	}
}

func foo1(x int) []func() {
	var fs []func()
	values := []int{1, 2, 3, 5}
	for _, val := range values {
		val := val
		fs = append(fs, func() {
			fmt.Printf("foo1 val = %d\n", x+val)
		})
	}
	return fs
}

(2)使用场景

//延迟执行
func ClosureDefer() {
	defer func() {
		fmt.Println("Deferred execution")
	}()

	fmt.Println("Main function")
}
//记忆化
func memoize(f func(int) int) func(int) int {
	cache := make(map[int]int)
	return func(n int) int {
		if result, ok := cache[n]; ok {
			return result
		}
		result := f(n)
		cache[n] = result
		return result
	}
}

func fibonacci(n int) int {
	if n <= 1 {
		return n
	}
	return fibonacci(n-1) + fibonacci(n-2)
}

func ClosureMem() {
	fib := memoize(fibonacci)
	fmt.Println(fib(10))
	fmt.Println(fib(5))
}
//函数工厂
func makeGreeter(name string) func() {
	return func() {
		fmt.Printf("Hello, %s!\n", name)
	}
}

func ClosureGreeter() {
	greeter := makeGreeter("John")
	greeter()
}
//事件回调
type Button struct {
	onClick func()
}

func (b *Button) Click() {
	if b.onClick != nil {
		b.onClick()
	}
}

func ClosureClick() {
	button := &Button{
		onClick: func() {
			fmt.Println("Button clicked!")
		},
	}

	button.Click()
}
//并发编程
func ClosureWg() {
	var counter int
	var wg sync.WaitGroup
	var mu sync.Mutex

	increment := func() {
		mu.Lock()
		counter++
		mu.Unlock()
		wg.Done()
	}

	for i := 0; i < 10; i++ {
		wg.Add(1)
		go increment()
	}

	wg.Wait()
	fmt.Println("Counter:", counter)
}

二.匿名函数

1.概念

     匿名函数是一种没有函数名的函数,通常用于在函数内部定义函数,或者作为函数参数进行传递。Go 语言支持匿名函数,可作为闭包。匿名函数是一个"内联"语句或表达式。匿名函数的优越性在于可以直接使用函数内的变量,不必申明。

      在Go语言中,函数是一等公民,这意味着函数可以像其他类型的值一样被操作、传递和赋值。匿名函数是一种特殊的函数,它没有固定的函数名,可以在代码中被直接定义和使用。匿名函数在Go语言中具有重要的地位,它们常用于实现闭包、函数式编程和并发编程等领域

//语法结构
func(a, b int) int {
    return a + b
}

2.使用场景

     将匿名函数赋值给变量、

     在函数内部使用匿名函数

     将匿名函数作为参数传递给其他函数

     闭包的实现  匿名函数在Go语言中常用于创建闭包(Closure)

3.代码案例

func NonameFunc() {
	// 定义一个匿名函数并将其赋值给变量add
	add := func(a, b int) int {
		return a + b
	}

	// 调用匿名函数
	result := add(3, 5)
	fmt.Println("3 + 5 =", result)

	// 在函数内部使用匿名函数
	multiply := func(x, y int) int {
		return x * y
	}

	product := multiply(4, 6)
	fmt.Println("4 * 6 =", product)

	// 将匿名函数作为参数传递给其他函数
	calculate := func(operation func(int, int) int, x, y int) int {
		return operation(x, y)
	}

	sum := calculate(add, 2, 8)
	fmt.Println("2 + 8 =", sum)

	// 也可以直接在函数调用中定义匿名函数
	difference := calculate(func(a, b int) int {
		return a - b
	}, 10, 4)
	fmt.Println("10 - 4 =", difference)

    //闭包
	c1 := counter() 
	fmt.Println(c1()) // 输出 1
	fmt.Println(c1()) // 输出 2

	c2 := counter()
	fmt.Println(c2()) // 输出 1
}
//counter() 函数返回一个匿名函数,这个匿名函数形成了闭包,持有了外部作用域中的 count 变量
func counter() func() int {
	count := 0
	return func() int {
		count++
		return count
	}
}

4.匿名函数与闭包关系

  闭包的实现也是一个匿名函数,一级指针在匿名函数里面指向“func1.f”, 在闭包中,指向闭包返回对象。闭包返回的包装对象是一个复合结构,里面包含匿名函数的地址,以及环境变量的地址。

  • 被闭包捕获的变量称为“自由变量”,在匿名函数实例未消亡时共享同个内存地址
  • 匿名函数及其“捕获”的自由变量被称为闭包
  • 同一个匿名函数可构造多个实例,每个实例内的 自由变量 地址不同
  • 匿名函数内部的局部变量在每次执行匿名函数时地址都是变换的

三.锁

四.断言

五.反射

六.递归

七.Deferred、Panic、Recover

八.Go 内存管理和GC回收

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值