go并发学习笔记

程序,进程,线程,协程

程序是指令和数据的有序集合,本身并没有运行的含义,静态概念。
电脑中’运行的程序’就是进程,可以理解为qq,播放器,浏览器,动态概念
播放器中的音乐,字幕等就是线程,一个进程可以有多个线程,线程是cpu调度和执行的单位
协程是轻量级的线程,类似函数,可以一次创建上百万个,进程和线程一般不超过1万个。

并发,并行

并发是一个cpu所有任务快速交替进行,有类似并行的假象。

并行是一个cpu执行一个任务,互不影响。

go并发Goroutine

区别于进程Process和线程Thread
go语言中,main函数不会等goroutine结束,main函数结束了 goroutine也结束了。

func main() {

	go hello() //与main函数同时快速交替进行,如果main先执行完,程序终止,不会继续执行hello()
	for i := 0; i < 100; i++ {
		fmt.Println("main",i)
	}
}

func hello() {
	for i := 0; i < 100; i++ {
		fmt.Println("hello",i)
	}
}
主Goroutine运行机制

大致如下4条
1、创建一个defer函数,用来做善后处理,比如处理painc
2、启动在后台用于清扫垃圾的goroutine,并设置GC可用标识
3、执行init函数
4、执行main函数

runtime包

可以获取系统信息的包
runtime.Gosched()让出时间切片,让其他goroutine先执行

func main() {

	go func() {
		for i := 0; i < 100; i++ {
			fmt.Println("go", i)
		}
	}()
	fmt.Println("获取GOROOT目录", runtime.GOROOT())
	fmt.Println("获取操作系统", runtime.GOOS)
	fmt.Println("获取cpu数量", runtime.NumCPU())
	for i := 0; i < 100; i++ {
		//让出时间切片,让其他goroutine先执行,大概率让成功
		runtime.Gosched()
		fmt.Println("main", i)
	}
}

runtime.Goexit()终止当前goroutine

func main() {
	go func() {
		fmt.Println("start")
		test()
		fmt.Println("end")
	}()

	time.Sleep(time.Second * 3) //防止main快速执行完,导致goroutine还没执行就结束了
}

func test() {
	defer fmt.Println("defer")
	//终止当前test函数
	//return
	runtime.Goexit() //终止当前goroutine
}

临界问题

并发编程中,临界资源处理不当,同时操作资源,会出现数据不一致的情况

func main() {
	a := 100
	go func() {
		a = 2
		fmt.Println("go", a)//2
	}()

	a = 3
	time.Sleep(time.Second * 3)
	fmt.Println("main", a)//2
}

售票问题,一共10张票多个窗口去卖,数据不一致:

互斥锁


系统内置的锁sync.Mutex

go语言中,不推荐锁的方式,如果协程数量太多了,都在排队,运行较慢,建议通信的方式,运行完通知下一位

售票问题用锁来解决就是运行的时候就上锁,其他看见锁了 就不执行,等解锁了在执行,类似公共卫生间,有锁就等待,没锁就进去,每次只有一个

var m sync.Mutex
var ticket int = 10

func main() {
	go saleTicket("售票口一")
	go saleTicket("售票口二")
	go saleTicket("售票口三")

	time.Sleep(time.Second * 10)

}
func saleTicket(name string) {
	for {
		time.Sleep(time.Millisecond * 300)
		m.Lock()//上锁
		if ticket > 0 {
			ticket--
			fmt.Printf("%s剩余%d张\n", name, ticket)
		} else {
			fmt.Printf("%s卖光了\n", name)
			m.Unlock()//解锁
			break
		}
		m.Unlock()//break之前也要解锁
	}
}

waitgroup

上一个例子中 为了防止main快速结束,导致goroutine没执行完就也跟着结束,使用了time.sleep(time.second*3)的函数,不过这个睡眠时间,不准确,可能提前结束,也可能晚结束,所以使用waitgroup()函数

也是sync下的包,3个方法
add(5) 添加5条协程
wait() 等待协程数量为0,结束,不然一直阻塞
done() 每次协程数减一
优化后的代码:

var m sync.Mutex        //创建锁
var ticket int = 10     //总共票数
var wg sync.WaitGroup	//创建协程等待组

func main() {
	wg.Add(3)    //3个协程
	go saleTicket("售票口一")
	go saleTicket("售票口二")
	go saleTicket("售票口三")
	wg.Wait()    //归零后释放,不然就阻塞着
}
func saleTicket(name string) {
	defer wg.Done() //执行完方法后协程组中的协程减一
	for {
		time.Sleep(time.Millisecond * 500)
		m.Lock() //上锁
		if ticket > 0 {
			ticket--
			fmt.Printf("%s剩余%d张\n", name, ticket)
		} else {
			fmt.Printf("%s卖光了\n", name)
			m.Unlock() //解锁
			break
		}
		m.Unlock() //解锁
	}
}

channel

go中不推荐用锁的方式共享数据,强烈推荐使用channel通信的方式,
通道一般在多个协程中,用来进行通信,发送和接收通道时另一个goroutine都是阻塞的,
创建通道 var 名字 chan 类型
可以使用make函数创建

func main() {
	var ch chan bool
	var wg sync.WaitGroup
	ch = make(chan bool)
	wg.Add(2)

	go func() {
		defer wg.Done()
		for i := 0; i < 100; i++ {
			fmt.Println("go", i)
		}
		ch <- true //通道中写数据时,接收方协程阻塞中
	}()

	ch1 := <-ch  //通道中读数据,读到数据后,继续运行
	for i := 0; i < 100; i++ {
		fmt.Println("main", i)
	}
	wg.Done()
	wg.Wait()
	fmt.Println(ch1)
}
死锁:

只写通道,不接收通道
只接受通道,不写通道
通道没有搭配goroutine,在同一层,读写通道.

关闭通道

close(通道)

func main() {
	var ch chan int
	ch = make(chan int)
	go test(ch)

	for true {
		time.Sleep(time.Second * 1)
		v, ok := <-ch //支持ok-idiom
		if !ok {
			fmt.Println("通道读取完毕")
			break
		}
		fmt.Println("通道中读取的数据", v)

	}
}

func test(ch chan int) {
	for i := 0; i < 10; i++ {
		ch <- i
	}
	close(ch)  //关闭通道,不然上面15行,接收不到通道的数据,会死锁
}
缓冲通道


通道原本基础上,增加了一个缓冲区,不是每次读写1个数据,可以同时写入大量数据后,慢慢进行读.

func main() {

	ch := make(chan string, 6) //创建带缓冲的通道,容量为6
	//往通道写数据
	go test(ch)
	//读数据,for range
	for s := range ch {
		time.Sleep(time.Second)
		fmt.Println("读数据", s)
	}

}

func test(ch chan string) {
	for i := 0; i < 10; i++ {
		ch <- strconv.Itoa(i)
		fmt.Println("写入数据为", i)
	}
	close(ch)
}

运行结果:
写入数据为 0
写入数据为 1
写入数据为 2
写入数据为 3
写入数据为 4
写入数据为 5
写入数据为 6
读数据 0
写入数据为 7
读数据 1
写入数据为 8
读数据 2
写入数据为 9
读数据 3
读数据 4
读数据 5
读数据 6
读数据 7
读数据 8
读数据 9

定向通道


通道创建的时候,默认是双向的,可读可写。
也可以设置为单向,只可读,或者只可写。
有些场景,只能读数据,或者只能写数据的时候会用到。

func main() {
	ch := make(chan int)
	go writeOnly(ch)  //记得加go
	go readOnly(ch)

	time.Sleep(3 * time.Second)//main函数睡3秒,不然结束的太快 来不及执行完go
}

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

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

}

select

Select是Go中的一个控制结构,长得有些像switch,Select随机执行case,case必须是通道操作,没有case运行就执行default,default也没有就阻塞,直到case通道运行。
一个case运行的时候,其他case不会运行。

func main() {
	ch := make(chan int)
	ch1 := make(chan int)

	go func() {
		time.Sleep(time.Second)
		ch <- 100  //向通道写
	}()

	go func() {
		time.Sleep(time.Second)
		ch1 <- 200  //向通道写

	}()

	select {
	case num1:=<-ch:    //从通道读
		fmt.Println("num1", num1)
	case num2 := <-ch1: //从通道读
		fmt.Println("num2", num2)
		//default:
		//time.Sleep(time.Second * 10)
		//fmt.Println("default")
	}

}

Timer定时器

可以设置阻塞时间,如设置3秒,就是3秒后执行,也可以手动提前关闭定时器。

func main() {

	timer := time.NewTimer(time.Second * 3) //创建一个3秒的Timer定时器
	fmt.Println(time.Now())
	timeChan := timer.C     //.C出来个time类型的通道
	fmt.Println(<-timeChan) //将通道的数据写到括号里
	go func() {
		<-timer.C //3s
		fmt.Println("3秒结束")

	}()
	fmt.Println(time.Now())
	time.Sleep(time.Second * 2)

	timer2 := time.NewTimer(time.Second * 10) //创建一个10秒的定时器
	go func() {
		<-timer2.C
		fmt.Println("")
	}()
	stop := timer2.Stop() //手动关闭定时器
	if stop {
		fmt.Println("关闭成功")
	}

}
after & afterfunc

time.after(d),直接返回time通道,和上面不一样的是,上面需要.C出来时间通道
time.after(d,f),创建time通道,并按时执行方法,返回值不是time通道,是timer定时器

func main() {
	times := time.After(time.Second * 3) //创建time通道
	fmt.Println(time.Now())   //2022-11-06 23:39:48.845842 +0800 CST m=+0.011156401
	after := <-times //返回time类型数据
	fmt.Println(after)     //2022-11-06 23:39:51.845842 +0800 CST m=+0.011156401

	time.AfterFunc(time.Second*3, test) //创建time定时器,并按时执行方法
	time.Sleep(time.Second * 5)
}

func test() {
	fmt.Println(time.Now())  //2022-11-06 23:39:54.845842 +0800 CST m=+0.011156401
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
《Go语言学习笔记.pdf》是一本关于Go语言学习学习笔记,内容丰富且简洁明了。本书从基础知识开始,逐步介绍了Go语言的语法、特性和常用库函数等。在学习笔记中,作者通过实际的示例和练习帮助读者理解Go语言的概念和用法。 第一章介绍了Go语言的起源和发展,为读者提供了对Go语言背景的整体了解。第二章讲解了Go语言的基本语法,例如变量声明、循环和条件语句等。通过大量的代码示例,读者能够更好地理解Go语言的语法和结构。 接下来的章节重点介绍了Go语言的并发编程和高级特性。第三章详细介绍了Go语言中的goroutine和channel,这是Go语言并发编程的核心机制。作者通过生动的示例代码和实际应用案例,向读者展示了如何使用goroutine和channel实现并发编程。 第四章和第五章分别介绍了Go语言中的面向对象编程和函数式编程。通过深入讲解Go语言中的结构体、接口和函数,读者能够更好地应用这些特性进行代码设计和开发。 最后几章则介绍了Go语言中常用的库函数和工具。例如,第六章介绍了Go语言中用于网络编程的net包和http包。读者可以学习到如何使用这些库函数构建基于网络的应用程序。 总的来说,《Go语言学习笔记.pdf》是一本非常实用的Go语言学习资料。通过阅读这本书,读者能够系统地学习和理解Go语言的基本概念和高级特性,为之后的Go语言开发打下坚实的基础。无论是初学者还是有一定编程经验的开发者,都能从中获得丰富的知识和经验。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

tigeraowu

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

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

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

打赏作者

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

抵扣说明:

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

余额充值