P103今日内容
P104 内容回顾
**在调用前面加上go关键字,就可以开启一个goroutine去执行函数。
goroutine对应函数执行完,该goroutine就结束了。
main函数启动会自动创建一个goroutine去执行main函数/。
**
sync.WaitGroup等待组,启动多个goroutine,也不知道哪个结束,要等待goroutine结束,就需要阻塞,之前使用sleep,但sleep不知道goroutine具体什么时候结束。
等待组有三个使用方法。
定义全局变量var wg sync.WaitGroup,申明一个结构体。
开启一个goroutine干活都需要登记一下,wg.Add(1)
工人结束后,要在goroutine内部执行,wg.Done(),告诉外面干完活了。计数器减一。
在干活的时候需要等,wg.Wait()
在主的main函数里等待其他goroutine是很常见的。
goroutine调度模型是GMP
goroutine和操作系统OS线程的区别。
一个进程里至少要有一个线程,线程是负责干活的。
goroutine是用户态的线程,操作系统是内核态的, 比内核态的线程更加轻量一点。开启一个操作系统线程至少要2M空间,开启一个goroutine只需要2KB,而且可以动态变化,最大扩展到1G。
可以轻松开启数十万个goroutine。
go的调度,其实是把M个goroutine调度到n个线程上干活。
goroutine设置运行时占用多少核,runtime.GOMAXPROCS,go1.5之后就是操作系统的逻辑核心数 。默认跑满cpu,在跑记日志 的时候可以设定占用资源小一点。
n就是起多个goroutine干活,for循环里,就是把这段代码执行三遍
work pool模式。动态调整资源占用,有三个goroutine,但是有5个任务。
为什么需要channel?
通过channel实现多个goroutine之间的通信。
CSP:通过通信来共享内存
channel是引用类型,make函数初始化之后才能使用。(slice,map,channel)
channel的声明:var ch chan 元素类型
初始化: ch = make(chan 元素类型,【缓冲区大小】
操作:
发送 ch <- 100
接收 x := <- ch
关闭 close(ch_
带缓冲区和不带缓冲区的区别,满了的话有人接才能放
需要从循环里去读取
通道里取完,应该返回个false,现在模拟一下,出现死锁,说明程序有问题。
缓冲区只有1个,存两个,就没有位置了,也需要等待。同样,只有1个取2个的时候,也要等
可以有其他的服务往channel传值
取值也循环去取
中间的停顿是阻塞在那里等
通道满了,再放进去就会阻塞,通道没了,再取就会阻塞,所以通道close掉,再取值就会返回false。
通道空了,再去接收值就是阻塞。满了,再发送值,也是阻塞。
通道里有值,但是关闭了,还能取值。取完返回对应0值和false。
select,一般考虑同一时刻有多个通道的时候使用,多路复用。
128个goroutine遍历500给任务,,循环从result取值,但是程序永远卡死,因为一直再取,但是通道关闭了
想要改造成取完就退出。死锁是因为results没有关闭,把所有计算结果放到results之后就可以关闭,就不会死锁了
for循环只是启动128个goroutine,具体完成不完成是不知道的
个数少点可以看到多少个
等的是goroutine结束,但是打印还来不及打印
匿名结构体不占空间
worker,做完往通道里发送值,现在不用cat,使用了一个匿名结构体。
使用了一个匿名结构体。
再写一个匿名函数,从notify取值,取5次,close掉通道
**先开启5个任务,5个任务相当于5个for循环,放进去任务,关闭jobs通道。
然后开启3个goroutine,这三个goroutine,从jobs取值,计算放到results里。
notify就发送信号,这个信号告诉已经执行5次。
**
5次在这里执行goroutine,专门从notifych里取5次值。5次后关闭results
这里就执行退出
P105 几个作业的问题
切完日志,往关闭的日志写就报错
首字母大写,但是返回值又是小写,是矛盾的,就不能对外暴露
切割的时候,第一条日志往心的文件里写,其他的还是往旧的写,旧的文件已经关闭,会报错
这一步没有生效
往里面写日志
现在就已经切换了
出现问题, 是因为指针的问题
这种传进来就是一个拷贝
三个点代表可变参数,传0个或是多个
是一个slice切片
多个值就是int类型的切片
空接口,空接口的切片接收任何值
当作一个整体传到f1里去了
空接口 切片第一个元素是int类型切片
slice嵌套slice,…点点点相当于拆开,一次次传进去
P106 异步写日志
之前是同步写,一条写成功后 ,再往下写
可以把日志作为一个结构体存到通道里
初始化
现在要 把生成的日志往通道里面 写
如果日志enable,就往通道里写,根据结构体造一个logmsg对象
造完对象,还需要写到通道里,但是如果通道满了,再往里放,就阻塞了,程序就不会返回了,代码不会往下走了。
可以使用select
可以先把日志拼出来
这里加上select,避免阻塞
如果取不出来,default可以先sleep一会
可以开启5个goroutine去写
现在就成了异步写日志
先构造一个通道(类似如果是字符串,就太大了,可以使用结构体。)
创建日志 实例的时候,要进行通道初始化,初始化后才能使用
设定大小变量,这样修改的时候,不会在代码里写了
开启一个线程应该就不会有问题
这里针对多个线程可能不行,并发的时候,切换的时候,有可能其他还在写日志
多个goroutine往同一个文件写多少有点问题
P107 互斥锁
某些goroutine启动要去访问全局变量,add函数循环加5000次,go起了2个add
先试试下面的,按顺序走完
开启两个goroutine去做这个事情,现在每次的值都不一样。
x是先去公共区域,拿到x,在这个基础上+1 ,再送回去。这个动作,两个goroutine都在做,都在自己区域里+1,往外放。正常是1个1个放,现在都是单独加1,比如两个50+1 ,其实是50+1+1,但是返回的时候变成加1个,无形中就少了。
一个人锁了之后,其他人需要等锁释放了才能使用。
互斥锁能保证同一时间只有一个goroutine去访问共享资源。
锁本身是个结构体变量
对一个公共资源操作的时候要加锁
现在就恢复正常了
P108 读写互斥锁
一个goroutine获取读锁之后,其他的读锁也会继续获得锁,如果是获取写锁,其他的groutine才能去读和写,写锁释放了,其他才能去读去写。‘
进行时间的加减
试试互斥锁,读完之后 解锁
写锁
go一次,wg.Add()加一个
都出来都是正确的
写10次,读1000次
2秒多
下面是读写互斥锁,级别高一点。
读写锁,读操作+R
读锁
写锁
速度很快,但是每次没加完的时候就已经在读了
sleep,先让加再跑起来
换回写锁,互斥锁
**当读操作大于写操作的时候,互斥锁,1000次读就不需要让别人等 **
P109 sync.Once示例
在sync某些包里,针对某些特定的场景,执行某些操作,只需要执行一次就可以了。
icons是个map,可以程序一启动就加载,也可以使用的时候才加载。但是这样并发是不安全的,因为loadIcons是用的全局变量。多个goroutine都去做初始化就不安全了。
现在的cpu可能保证每个goroutine都是去串行的
确保某个函数或操作只执行一次,sync.Once里也是个结构体,一个锁,加上一个标志位,第一次做的时候判断标志位是否为true,true代表已经做过,就往下走,false代表没有执行过,就先加锁再执行,执行完就释放锁。
通道数只能关闭一次
Once是确保通道数只关闭一次
执行了多个goroutine,如果都要关闭,肯定就报错,
这个函数要求没有参数没有返回值
就可以做一个闭包
先加锁,defer确保最后释放锁,置为1后,后面的goroutine就直接跳过了
写一个闭包函数满足once方法传递的一个无返回值函数,变量会去上面找。
P110 sync.Map示例
做配置文件的时候,一般都需要map类型来存储,go内置的map不是并发安全的,多个goroutine访问会出现问题。
起20个goroutine,每次起一个设置一个值,返回一个值
并发写map就报错,并发超过20的时候编译器就会报错
加锁
加了锁,试试超过21个goroutine
考虑到这个问题,go提供了并发安全版的map
set可以使用提供的方法Store,存储,必须使用store存值,开箱即用,不用初始化。
P111 atomic原子性操作
atomic原子
之前并发修改全局的是会有问题的,就需要加锁
atomic这个包提供的一个addint64对于 int64的累加操作。
每一次都不一样,10万个goroutine肯定去读这个全局变量,会并发不安全
一种原始方式是加全局锁
用原子包里提供的方法对x做加一操作。
load是全局安全读取值,store全局存值,add全局修改
swap全局交换,compare全局比较并交换操作。
如果值相当0,设置成100
false还是原来的值
P112互联网协议介绍
数据链路层,有帧的概念,以太网的概念,广播arp
ip地址是网络层
tcp和udp
P113 TCP服务端客户端开发
socket属于一个抽象层,用户写程序不用关心其他的协议方法,只关注调用socket规定的相关函数就行。socket把下面都屏蔽掉了,不需要关心下面的
写一个客户端
把值传进去
scanln遇到空格就结束了,还需要改一下,使用bufio
另外再开一个客户端
服务端也需要for循环,现在这样是一个人收一次,其他人收不到了
改成for循环
可以写一个回复
P114 解决粘包的问题
tcp是基于流式的数据,没有明确的开始和结束,这次结束和下次要发的一起发给你了,这就是tcp年粘包。
nagle算法提交一段消息给tcp的时候,tcp并不立刻发送这条数据,等待是否有其他的包,如果有其他的包,一起发送。副作用就是出现粘包。
会造成tcp缓冲区中存放了几段数据。
创建一个读,读1024个字节。
本地监听30000端口
客户端
并不是我们想的一次发一句,因为nagle算法,不满包,会等待一段时间,如果有数据。把后面的数据放在一起
等待一下就是,每次运行一次
可以规定自己前面发的包的4个字节代表大小,就可以去里面判断大小,每次读多少
编码一个方法,解码一个方法
编码方法把消息大小算出来
高位可以写在左边也可以写在右边,从内存里读数据,从左开始读还是右开始,就需要告诉它
这是小端的方式,往包里写长度
包大小解出来,读指定大小的数据。
发消息的时候写
server端收消息,从编码里取需要读多少大小的数据
这样数据就发送过来了
这样每次收集的数据都是固定大小的数据
P115 UDP客户端服务端
UDP属于一个不可靠吗,没有时序的协议
不需要建立链接直接通信
客户端
客户端从中间就可以收发信息,无限循环,bufio造一个读对象
收到转换大写