014.go语言中的协程

目录

1.多线程相关概念

2.Go语言中的协程(Goroutine)

3.主Goroutine

4.runtime包

5.临界资源安全问题:多个线程操作同一个资源会发生临界资源安全问题

6.互斥锁:解决临界资源安全问题"不推荐"

7.同步等待组(计数器):优化上面主线程睡眠2秒的问题

8.通道

9.通道的注意事项

10.关闭通道

11.缓冲通道

12.定向通道

13.Select

14.Timer定时器


1.多线程相关概念

进程:一个程序执行起来会开辟一个或多个进程,至少有一个进程
线程:一个进程可以开辟一个或多个线程,至少有一个线程
并发:同一时间多个线程抢夺cpu资源,交替执行
并行:同一时间多个线程同时执行

2.Go语言中的协程(Goroutine)

协程概念:Go语言使用协程(Goroutine)实现并发,是一个轻量级的线程,一个协程在堆上初始分配的大小只有4K
开启协程:开启协程只需要在调用函数或方法时,在前面加上go关键字即可
注意事项:
    1.当开启一个新的协程,程序会立刻回到当前协程继续执行,不会等待协程执行结束后再执行
    2.如果main协程先执行完,程序会终止,不会等待其他协程执行结束

func main() {
	//开启一个新的协程
	go func() {
		for i := 0; i < 100; i++ {
			fmt.Println("goroutine-->", i)
		}
	}()
	//main协程
	for i := 0; i < 100; i++ {
		fmt.Println("main-->", i)
	}
}

3.主Goroutine

主Goroutine做了哪些操作
1.设定申请的栈空间的最大尺寸,32位计算机:250M,64位计算机:1G
2.初始化
    2.1:创建一个特殊的defer语句,用于在主Goroutine退出时做一些善后处理,因为主Goroutine也可能报错
    2.2:启动专用于后台清理内存垃圾的Goroutine,也叫Gc线程
    2.3:执行main包下的init函数
    2.4:执行main函数
    2.5:执行完后检查主goroutine是否引发了运行时恐慌,并进行必要的处理
    2.6:程序运行完后主goroutine会结束自己的运行

4.runtime包

1.获取系统信息
	1.获取GoRoot目录:runtime.GOROOT()
	2.获取操作系统:runtime.GOOS
	3.获取cpu核心数量:runtime.NumCPU()

2.让出时间片,让其他代码先执行,不一定能让成功:runtime.Gosched()

3.终止当前Goroutine的执行:runtime.Goexit()

//获取系统信息
func main() {
	fmt.Println("GoRoot目录:", runtime.GOROOT())
	fmt.Println("System:", runtime.GOOS)
	fmt.Println("cpu核心数量:", runtime.NumCPU())
}

//让出时间片
func main() {
	//开启goroutine
	go func() {
		for i := 0; i < 10; i++ {
			fmt.Println("goroutine-->", i)
		}
	}()
	//主goroutine中的代码
	for i := 0; i < 10; i++ {
		runtime.Gosched() //让出时间片,不一定能让成功
		fmt.Println("main-->", i)
	}
}

//终止当前Goroutine的执行
func main() {
	go func() {
		fmt.Println("start")
		runtime.Goexit()
		fmt.Println("end")
	}()
	time.Sleep(time.Second * 2) //休眠两秒,避免主goroutine提前结束,保证goroutine能正常执行完
}

5.临界资源安全问题:多个线程操作同一个资源会发生临界资源安全问题

卖票案例:出现了临界资源安全问题

// 总共十张票
var ticket int = 10

// 卖票
func saleTicket(name string) {
	for {
		if ticket > 0 {
			fmt.Println(name, "正在卖第", ticket, "张票")
			ticket--
		} else {
			fmt.Println("票已售完")
			break
		}
	}
}

// 四个窗口卖票
func main() {
	go saleTicket("窗口一")
	go saleTicket("窗口二")
	go saleTicket("窗口三")
	go saleTicket("窗口四")

	time.Sleep(time.Second * 2) //休眠2秒,防止主goroutine提前结束
}

6.互斥锁:解决临界资源安全问题"不推荐"

        Go并发编程有这么一句话:"不要以共享内存的方式去通信,而要以通信的方式去共享内存",锁机制就是以共享内存的方式去通信,而通道是以通信的方式去共享内存;

案例:使用sync.Mutex锁解决多线程临界资源安全问题

// 总共十张票
var ticket int = 10

// 1.定义一个锁
var mutex sync.Mutex

// 卖票
func saleTicket(name string) {
	for {
		//2.操作共享资源之前先上锁
		mutex.Lock()
		if ticket > 0 {
			fmt.Println(name, "正在卖第", ticket, "张票")
			ticket--
			//3.操作完共享资源后解锁
			mutex.Unlock()
		} else {
			fmt.Println("票已售完")
			//3.程序结束也要解锁
			mutex.Unlock()
			break
		}
	}
}

// 四个窗口卖票
func main() {
	go saleTicket("窗口一")
	go saleTicket("窗口二")
	go saleTicket("窗口三")
	go saleTicket("窗口四")

	time.Sleep(time.Second * 2) //休眠2秒,防止主goroutine提前结束
}

7.同步等待组(计数器):优化上面主线程睡眠2秒的问题

        上面代码主线程睡眠两秒是为了解决主线程提前结束问题,如果睡眠两秒后,协程还是没有处理完,这时候代码就会终止,怎么办?

可以使用同步等待组解决这个问题

1.定义一个同步等待组:var wg sync.WaitGroup

2.每执行完一个协程,协程数减一:defer wg.Done()

3.等待协程数归0自动往下执行:wg.Wait()

案例:使用同步等待组解决主线程何时结束问题

// 定义一个锁
var mutex sync.Mutex

// 1.定义一个同步等待组
var wg sync.WaitGroup

// 总共十张票
var ticket int = 10

// 卖票
func saleTicket(name string) {
	//2.每执行完一个协程数减一
	defer wg.Done()
	for {
		//操作共享资源之前先上锁
		mutex.Lock()
		if ticket > 0 {
			fmt.Println(name, "正在卖第", ticket, "张票")
			ticket--
			//操作完共享资源后解锁
			mutex.Unlock()
		} else {
			fmt.Println("票已售完")
			//程序结束也要解锁
			mutex.Unlock()
			break
		}
	}
}

// 四个窗口卖票
func main() {
	//设置协程数
	wg.Add(4)
	go saleTicket("窗口一")
	go saleTicket("窗口二")
	go saleTicket("窗口三")
	go saleTicket("窗口四")

	//3.等待协程数归0自动往下执行
	wg.Wait()
}

8.通道

通道的使用

1.声明通道:只声明不创建是nil
        var 通道名 chan 通道里传输的数据类型

2.创建通道
        通道名 =make(chan 通道里传输的数据类型)

3.向通道中写入数据
        通道名 <- 数据

4.从通道中拿数据
        变量名 := <- 通道名


func main() {
	//声明通道
	var ch chan bool
	//创建通道
	ch = make(chan bool)
	go func() {
		for i := 0; i < 10; i++ {
			fmt.Println(i)
		}
		//协程运行完后往通道写入一个数据
		ch <- true
	}()
	//从通道中取出数据,证明协程执行结束了
	data := <-ch
	fmt.Println("程序运行结束", data)
}

9.通道的注意事项

1.通道是阻塞的,只能存储一个数据,想再次存入,只能先取出通道中的数据,否则会产生死锁

2.如果创建一个通道后,直接读取数据,没有向通道中写入数据,程序会产生死锁

3.通道是同步的,同一时间只能有一个goroutine操作通道

4.通道是处理不同goroutine之间的通信,通道的发送和接收必须在不同goroutine,否则会产生死锁

10.关闭通道

//通道使用完后关闭通道
func main() {
	// 创建一个通道
	ch := make(chan int)
	//协程向通道中写入数据:一次写入一条,主协程取出来后继续写入
	go func() {
		for i := 0; i < 10; i++ {
			ch <- i
		}
		//写入结束,关闭通道
		close(ch)
	}()
	//从通道中取出数据
	for v := range ch {
		fmt.Println(v)
	}
}

11.缓冲通道

/*
    缓冲通道:带有缓冲区的通道,缓冲区满时才会阻塞等待goroutine取出通道的数据
    通道名 := make(chan 通道中传输的数据类型, 通道大小)
*/

func main() {
	//定义一个普通的通道
	ch1 := make(chan int)
	fmt.Println(len(ch1), cap(ch1)) //0 0
	//定义一个缓冲通道
	ch2 := make(chan int, 5)
	fmt.Println(len(ch2), cap(ch2)) //0 5

	//向通道中存入数据
	go func() {
		for i := 0; i < 10; i++ {
			ch2 <- i
			fmt.Println("写入的数据:", i)
		}
		//关闭通道
		close(ch2)
	}()
	//停顿两秒观察效果:发现一次写入了5个数据到通道,才来读取的
	time.Sleep(time.Second * 2)
	//读取数据
	for v := range ch2 {
		fmt.Println("读取出的数据:", v)
	}
}

12.定向通道


定向通道:只能读或只能写的通道,也叫单项通道;
一般作为函数的参数,控制这个函数只能向通道中写入数据或只能从通道中读取数据

	1.只能向通道中写入数据; 通道名 := make(chan <- 通道中传输数据的类型)
	2.只能从通道中读取数据; 通道名 := make(<- chan 通道中传输数据的类型)



func main() {
	//定义一个可读可写的通道
	ch := make(chan int)

	go writeOnly(ch)
	go readOnly(ch)

	time.Sleep(time.Second) //睡眠一秒,防止main协程提前结束

}

// 只写
func writeOnly(ch chan<- int) {
	ch <- 100
}

// 只读
func readOnly(ch <-chan int) {
	data := <-ch
	fmt.Println(data)
}

13.Select

/*
select:是一个控制结构,会在多个满足条件的条件中,随机选择一个执行;如果没有条件满足就会阻塞,知道有条件满足执行
注意:每个case都必须是一个通道的操作
*/

func main() {
	ch1 := make(chan int)
	ch2 := make(chan int)
	go func() {
		ch1 <- 100
	}()
	go func() {
		ch2 <- 200
	}()

	select {
	case data1 := <-ch1:
		fmt.Println(data1)
	case data2 := <-ch2:
		fmt.Println(data2)
		/*default: 写了default不会阻塞
		fmt.Println("没匹配到")*/
	}
}

14.Timer定时器

/*
1.创建一个定时器:在指定时间后触发;定时器名 := time.NewTimer(时间)  返回的是一个定时器
2.提前关闭定时器;定时器名.Stop()
3.创建一个定时器:在指定时间后触发;定时器名 := time.NewTimer(时间)  返回的是一个通道
4.定时执行一个函数:time.AfterFunc(时间,函数)
*/

func main() {
	//1.创建一个定时器
	fmt.Println("当前时间:", time.Now())
	timer := time.NewTimer(time.Second * 3)
	timerChan := timer.C
	fmt.Println("定时器执行时间:", <-timerChan)
	//---------------
	//2.提前关闭定时器
	timer2 := time.NewTimer(time.Second * 5)
	go func() {
		fmt.Println("goroutine取到了数据:", <-timer2.C)
	}()
	flag := timer2.Stop()
	if flag {
		fmt.Println("timer2停止了")
	}
	//3.创建一个定时器
	fmt.Println("当前时间:", time.Now())
	timeChan2 := time.After(time.Second * 3)
	fmt.Println("定时器2执行时间:", <-timeChan2)
	//4.定时执行一个函数
	time.AfterFunc(time.Second*3, func() {
		fmt.Println("函数执行了")
	})

	time.Sleep(time.Second * 5) //主程序睡眠5秒,防止提前结束
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值