目录
聊聊Go的goroutine和Channel
一、使用channel等待任务结束
sync.WaitGroup的用法" >sync.WaitGroup的用法
抽象代码" >抽象代码
二、使用select进行调度" >二、使用select进行调度
计时器的使用" >计时器的使用
首页 后端开发 Golang 聊聊Go的并发编程 (二)

聊聊Go的并发编程 (二)

Jul 07, 2021 pm 04:16 PM
go

聊聊Go的goroutine和Channel

    • 一、使用channel等待任务结束
      • sync.WaitGroup的用法
      • 抽象代码
    • 二、使用select进行调度
      • 计时器的使用
    • 三、总结

    相关文章推荐:《聊聊Go的并发编程 (一)

    一、使用channel等待任务结束

    使用案例还是在第一篇的第二节中写的代码,不过这里只需要一段即可。

    package mainimport (
    	"fmt"
    	"time")func createWorker(id int) chan<- int {
    	c := make(chan int)
    	go worker(id, c)
    	return c}func worker(id int, c chan int) {
    	for n := range c {
    		fmt.Printf("Worker %d receive %c\n", id, n)
    	}}func channelDemo() {
    	var channels [10]chan<- int
    	for i := 0; i < 10; i++ {
    		channels[i] = createWorker(i)
    	}
    
    	for i := 0; i < 10; i++ {
    		channels[i] <- &#39;a&#39; + i	}
    
    	for i := 0; i < 10; i++ {
    		channels[i] <- &#39;A&#39; + i	}
    	time.Sleep(time.Millisecond)}func main() {
    	channelDemo()}
    登录后复制

    这里咔咔将原始源码放在这里,如果你想跟着文章的节奏走,可以放到你的编辑器中进行操作。

    那这段代码的问题是在哪里呢?

    可以看到在channelDemo函数最后使用了一个sleep,这玩意在程序中可不能乱用。

    说到这里给大家讲一个小故事,咔咔之前在网上看到一段就是加了sleep的代码。

    然后一个新手程序员不明白为什么要加这个sleep,然后问题项目经理,项目经理说老板发现程序慢之后会找咱们优化,每一次优化把这个sleep的时间缩短即可。让老板感觉到我们在做事情。

    新手就是新手对不懂得代码都会进行标注,然后就写了一句注释“项目经理要求这里运行缓慢,老板让优化时,代码得到明显的速度提升”。

    这句话很不巧的是被老板给看见了,老板不认识代码,但文字还是认识的哈!于是,项目经理下马。

    所以说对于sleep大多数都是一个测试状态,坚决不会出现在线上的,所以呢?就要解决代码中的这个sleep。

    那么大家在回忆一下,在这里为什么要加sleep呢?

    发送到channel的数据都是在另一个goroutine中进行并发打印的,并发打印就会出现问题,因为根本不会知道什么时候才打印完毕。

    所以说这个sleep就会为了应对这个不知道什么时候打印完的问题,给个1毫秒让进行打印。

    这种做法是非常不好的,接下来看看使用一种新的方式来解决这个问题。

    以下代码是修改完的代码。

    package mainimport (
    	"fmt")type worker struct {
    	in   chan int
    	done chan bool}func createWorker(id int) worker {
    	w := worker{
    		in:   make(chan int),
    		done: make(chan bool),
    	}
    	go doWorker(id, w.in, w.done)
    	return w}func doWorker(id int, c chan int, done chan bool) {
    	for n := range c {
    		fmt.Printf("Worker %d receive %c\n", id, n)
    		done <- true
    	}}func channelDemo() {
    	var workers [10]worker	for i := 0; i < 10; i++ {
    		workers[i] = createWorker(i)
    	}
    
    	for i := 0; i < 10; i++ {
    		workers[i].in <- &#39;a&#39; + i		<-workers[i].done	}
    
    	for i := 0; i < 10; i++ {
    		workers[i].in <- &#39;A&#39; + i		<-workers[i].done	}}func main() {
    	channelDemo()}
    登录后复制

    将这些代码复制到你的本地,然后再来看一下都做了什么改动。

    • 首先为了参数传递方便,建立了一个结构体worker
    • 并且把之前的worker方法改为了doWorker
    • 这个时候createWorker方法返回值就不能是之前的channel了,而是创建的结构体worker
    • 然后在createWorker方法里边把channel全部创建好。并且使用结构体给doWorker传递参数。
    • 最终返回的就是结构体。
    • 最后一步就是给channelDemo方法里边发送数据的俩个循环里边接收一下workers[i]的值即可。

    看一下打印结果

    打印结果

    是不是有点懵,这怎么成有序的了,如果是并行的那还有必要开那10个worker,直接按照顺序打印就好了。

    现在就来解决这个问题,我不希望发一个任务然后等它结束。

    最好的就是把他们全部发出去,等待它们全部结束再退出来。

    代码实现如下

    package mainimport (
    	"fmt")type worker struct {
    	in   chan int
    	done chan bool}func createWorker(id int) worker {
    	w := worker{
    		in:   make(chan int),
    		done: make(chan bool),
    	}
    	go doWorker(id, w.in, w.done)
    	return w}func doWorker(id int, c chan int, done chan bool) {
    	for n := range c {
    		fmt.Printf("Worker %d receive %c\n", id, n)
    		done <- true
    	}}func channelDemo() {
    	var workers [10]worker	for i := 0; i < 10; i++ {
    		workers[i] = createWorker(i)
    	}
    
    	for i, worker := range workers {
    		worker.in <- &#39;a&#39; + i	}
    
    	for i, worker := range workers {
    		worker.in <- &#39;A&#39; + i	}
    
    	for _, worker := range workers {
    		<-worker.done		<-worker.done	}}func main() {
    	channelDemo()}
    登录后复制

    在这里再进行打印看一下结果,你会发现代码是有问题的。

    报错信息

    为什么将小写的字母打印出来,而打印大写字母时发生了报错呢?

    这个就要追溯到代码中了,因为我们代码本身就写的有问题。

    还是回归到本文长谈的一个问题,那就是对于所有的channel有发送数据就必须有接收数据,如果没有接收数据就会报错。

    那么在代码中你能看出是那块只进行了发送数据,而没有接收数据吗?

    出现问题的代码

    这个问题就是当给channel把小写字母发送了后,就会到进入到doWorker方法,然后给done发送了一个true,但是接收done的方法是在后面,也就是说第二个发送大写字母时,就会发送循环的等待。

    解决这个问题也很简单,我们只需要并发的发送done即可。

    并发done

    看到打印结果也是正确的。

    本文给的这个案例在一般项目中是不会出现的,所以说不用纠结于此。

    给的案例就是为了让大家更熟悉channel的机制而已。

    对于这个解决方法还有一个方案解决,请看代码。

    第二种方案

    将代码还原到之前,然后在每一个发送字母的下面循环接收done即可。

    对于这种多任务等待方式在go中有一个库是可以来做这个事情,接下来看一下。

    sync.WaitGroup的用法

    对于sync.WaitGroup的用法咔咔就不一一介绍了,简单的看一下源码的实现即可。

    package mainimport (
    	"fmt"
    	"sync")type worker struct {
    	in chan int
    	wg *sync.WaitGroup}func createWorker(id int, wg *sync.WaitGroup) worker {
    	w := worker{
    		in: make(chan int),
    		wg: wg,
    	}
    	go doWorker(id, w.in, wg)
    	return w}func doWorker(id int, c chan int, wg *sync.WaitGroup) {
    	for n := range c {
    		fmt.Printf("Worker %d receive %c\n", id, n)
    		wg.Done()
    	}}func channelDemo() {
    	var wg sync.WaitGroup	var workers [10]worker	for i := 0; i < 10; i++ {
    		workers[i] = createWorker(i, &wg)
    	}
    	// 添加20个任务
    	wg.Add(20)
    	for i, worker := range workers {
    		worker.in <- &#39;a&#39; + i	}
    	for i, worker := range workers {
    		worker.in <- &#39;A&#39; + i	}
    	wg.Wait()}func main() {
    	channelDemo()}
    登录后复制

    这份源码也是非常简单的,具体修改得东西咔咔简单介绍一下。

    • 首先取消了channelDemo这个方法中关于done的channel。
    • 使用了sync.WaitGroup,并且给createWorker方法传递sync.WaitGroup
    • createWorker方法使用了 worker的结构体。
    • 所以要先修改worker结构体,将之前的done改为wg *sync.WaitGroup即可
    • 这样就可以直接用结构体的数据。
    • 接着在doWorker方法中把最后一个参数done改为wg *sync.WaitGroup
    • 将方法中的done改为wg.Done()
    • 最后一步就是回到函数channelDemo中把任务数添加进去,然后在代码最后添加一个等待即可。

    关于这块的内容先知道这么用即可,咔咔后期会慢慢的补充并且深入。

    抽象代码

    这块的代码看起来不是那么的完美的,接下来抽象一下。

    问题代码

    这块代码有没有发现有点蹩脚,接下来我们使用函数式编程进行简单的处理。

    package mainimport (
    	"fmt"
    	"sync")type worker struct {
    	in   chan int
    	done func()}func createWorker(id int, wg *sync.WaitGroup) worker {
    	w := worker{
    		in: make(chan int),
    		done: func() {
    			wg.Done()
    		},
    	}
    	go doWorker(id, w)
    	return w}func doWorker(id int, w worker) {
    	for n := range w.in {
    		fmt.Printf("Worker %d receive %c\n", id, n)
    		w.done()
    	}}func channelDemo() {
    	var wg sync.WaitGroup	var workers [10]worker	for i := 0; i < 10; i++ {
    		workers[i] = createWorker(i, &wg)
    	}
    	// 添加20个任务
    	wg.Add(20)
    	for i, worker := range workers {
    		worker.in <- &#39;a&#39; + i	}
    	for i, worker := range workers {
    		worker.in <- &#39;A&#39; + i	}
    	wg.Wait()}func main() {
    	channelDemo()}
    登录后复制

    这块代码看不明白就先放着,写的时间长了,你就会明白其中的含义了,学习东西不要钻牛角尖。

    二、使用select进行调度

    开头先给一个问题,假设现在有俩个channel,谁来的快先收谁应该怎么做?

    package mainimport (
    	"fmt"
    	"math/rand"
    	"time")func generator() chan int {
    	out := make(chan int)
    	go func() {
    		i := 0
    		for {
    			// 随机睡眠1500毫秒以内
    			time.Sleep(
    				time.Duration(rand.Intn(1500)) *
    					time.Millisecond)
    			// 往out这个channel发送i值
    			out <- i
    			i++
    		}
    	}()
    	return out}func main() {
    	// 这里需要明白如果代码为var c1, c2 chan int  则c1和c2都为nil
    	// 在 select里面也是可以使用的,只不过是堵塞状态!
    	var c1, c2 = generator(), generator()
    	for {
    		/**
    		select 方式进行调度
    		        使用场景:比如有多个通道,但我打算是哪一个通道先给我数据,我就先执行谁
    		        这个select 可以是并行执行 channel管道
    		*/
    		select {
    		case n := <-c1:
    			fmt.Printf("receive from c1 %d\n", n)
    		case n := <-c2:
    			fmt.Printf("receive from c2 %d\n", n)
    		}
    	}}
    登录后复制

    以上就是代码实现,代码注释也写的非常的清晰明了,就不过多的做解释了。

    主要用法还是对channel的使用,在带上了一个新的概念select,可以在多个通道,那个通道先发送数据,就先执行谁,并且这个select也是可以并行执行channel管道。

    在上文写的createWorkerworker俩个方法还记得吧!接下来就不在select里边直接打印了。

    就使用之前写的俩个方法融合在一起,咔咔已将将源码写好了,接下来看一下实现。

    package mainimport (
    	"fmt"
    	"math/rand"
    	"time")func worker(id int, c chan int) {
    	for n := range c {
    		fmt.Printf("Worker %d receive %d\n", id, n)
    	}}func createWorker(id int) chan<- int {
    	c := make(chan int)
    	go worker(id, c)
    	return c}func generator() chan int {
    	out := make(chan int)
    	go func() {
    		i := 0
    		for {
    			// 随机睡眠1500毫秒以内
    			time.Sleep(
    				time.Duration(rand.Intn(1500)) *
    					time.Millisecond)
    			// 往out这个channel发送i值
    			out <- i
    			i++
    		}
    	}()
    	return out}func main() {
    	// 这里需要明白如果代码为var c1, c2 chan int  则c1和c2都为nil
    	// 在 select里面也是可以使用的,只不过是堵塞状态!
    	var c1, c2 = generator(), generator()
    	// 直接调用createWorker方法,返回的就是一个channel
    	w := createWorker(0)
    	for {
    		/**
    		select 方式进行调度
    		        使用场景:比如有多个通道,但我打算是哪一个通道先给我数据,我就先执行谁
    		        这个select 可以是并行执行 channel管道
    		*/
    		select {
    		case n := <-c1:
    			w <- n		case n := <-c2:
    			w <- n		}
    	}}
    登录后复制

    运行代码

    运行结果

    看到运行结果得知也是没有问题的。

    这段代码虽然运行没有任何问题,但是这样有什么缺点呢?

    缺点代码

    可以看下这段代码n := <-c1:这里先收了一个值,然后在下边代码w <- n又会阻塞住,这个是不好的。

    那么希望是怎么执行的呢?

    改成这样的模式

    这种模式是在select中既可以收数据,也可以发数据,目前这个程序是编译不过的,请看修改后的源码。

    package mainimport (
    	"fmt"
    	"math/rand"
    	"time")func worker(id int, c chan int) {
    	for n := range c {
    		fmt.Printf("Worker %d receive %d\n", id, n)
    	}}func createWorker(id int) chan<- int {
    	c := make(chan int)
    	go worker(id, c)
    	return c}func generator() chan int {
    	out := make(chan int)
    	go func() {
    		i := 0
    		for {
    			// 随机睡眠1500毫秒以内
    			time.Sleep(
    				time.Duration(rand.Intn(1500)) *
    					time.Millisecond)
    			// 往out这个channel发送i值
    			out <- i
    			i++
    		}
    	}()
    	return out}func main() {
    	// 这里需要明白如果代码为var c1, c2 chan int  则c1和c2都为nil
    	// 在 select里面也是可以使用的,只不过是堵塞状态!
    	var c1, c2 = generator(), generator()
    	// 直接调用createWorker方法,返回的就是一个channel
    	var worker = createWorker(0)
    	// 这个n如果放在for循环里边,就会一直打印0,因为从c1和c2收数据需要时间,所以会把0直接传给worker
    	n := 0
    	// 使用这个标识告诉有没有值
    	hasValue := false
    	for {
    		// 利用nil  channel的特性
    		var activeWorker chan<- int
    		if hasValue {
    			activeWorker = worker		}
    		/**
    		select 方式进行调度
    		        使用场景:比如有多个通道,但我打算是哪一个通道先给我数据,我就先执行谁
    		        这个select 可以是并行执行 channel管道
    		*/
    		select {
    		case n = <-c1:
    			// 收到值的话就标记为true
    			hasValue = true
    		case n = <-c2:
    			// 收到值的话就标记为true
    			hasValue = true
    		case activeWorker <- n:
    			hasValue = false
    		}
    	}}
    登录后复制

    这个模式还是有缺点的,因为n收c1和c2的速度跟消耗的速度是不一样的。

    假设c1的生成速度特别快,一下子生成了1,2,3。那么最后输出的数据有可能就只有3,而1和2就无法输出了。

    这个场景也是非常好模拟的,只需要在打印的位置加上一点延迟时间即可。

    运行结果

    此时你会看到运行结果为0、7、12、20…中间很多的数字都没来得急打印。

    因此我们就需要把收到的n存下来进行排队输出。

    package mainimport (
    	"fmt"
    	"math/rand"
    	"time")func worker(id int, c chan int) {
    	for n := range c {
    		// 手动让消耗速度变慢
    		time.Sleep(5 * time.Second)
    		fmt.Printf("Worker %d receive %d\n", id, n)
    	}}func createWorker(id int) chan<- int {
    	c := make(chan int)
    	go worker(id, c)
    	return c}func generator() chan int {
    	out := make(chan int)
    	go func() {
    		i := 0
    		for {
    			// 随机睡眠1500毫秒以内
    			time.Sleep(
    				time.Duration(rand.Intn(1500)) *
    					time.Millisecond)
    			// 往out这个channel发送i值
    			out <- i
    			i++
    		}
    	}()
    	return out}func main() {
    	// 这里需要明白如果代码为var c1, c2 chan int  则c1和c2都为nil
    	// 在 select里面也是可以使用的,只不过是堵塞状态!
    	var c1, c2 = generator(), generator()
    	// 直接调用createWorker方法,返回的就是一个channel
    	var worker = createWorker(0)
    	// 用来收n的值
    	var values []int
    	for {
    		// 利用nil  channel的特性
    		var activeWorker chan<- int
    		var activeValue int
    		// 判断当values中有值时
    		if len(values) > 0 {
    			activeWorker = worker			// 取出索引为0的值
    			activeValue = values[0]
    		}
    		/**
    		select 方式进行调度
    		        使用场景:比如有多个通道,但我打算是哪一个通道先给我数据,我就先执行谁
    		        这个select 可以是并行执行 channel管道
    		*/
    		select {
    		case n := <-c1:
    			// 将收到的数据存到values中
    			values = append(values, n)
    		case n := <-c2:
    			// 将收到的数据存到values中
    			values = append(values, n)
    		case activeWorker <- activeValue:
    			// 送出去后就需要把values中的第一个值拿掉
    			values = values[1:]
    		}
    	}}
    登录后复制

    以上就是实现代码

    此时在来看运行结果。

    运行结果

    运行结果没有漏掉数据,并且也是无序的,这样就非常好了。

    计时器的使用

    上面的这个程序是退出不了的,我们想让它10s后就直接退出怎么做呢?

    那就需要使用计时器来进行操作了。

    package mainimport (
    	"fmt"
    	"math/rand"
    	"time")func worker(id int, c chan int) {
    	for n := range c {
    		// 手动让消耗速度变慢
    		time.Sleep(time.Second)
    		fmt.Printf("Worker %d receive %d\n", id, n)
    	}}func createWorker(id int) chan<- int {
    	c := make(chan int)
    	go worker(id, c)
    	return c}func generator() chan int {
    	out := make(chan int)
    	go func() {
    		i := 0
    		for {
    			// 随机睡眠1500毫秒以内
    			time.Sleep(
    				time.Duration(rand.Intn(1500)) *
    					time.Millisecond)
    			// 往out这个channel发送i值
    			out <- i
    			i++
    		}
    	}()
    	return out}func main() {
    	// 这里需要明白如果代码为var c1, c2 chan int  则c1和c2都为nil
    	// 在 select里面也是可以使用的,只不过是堵塞状态!
    	var c1, c2 = generator(), generator()
    	// 直接调用createWorker方法,返回的就是一个channel
    	var worker = createWorker(0)
    	// 用来收n的值
    	var values []int
    	// 返回的是一个channel
    	tm := time.After(10 * time.Second)
    	for {
    		// 利用nil  channel的特性
    		var activeWorker chan<- int
    		var activeValue int
    		// 判断当values中有值时
    		if len(values) > 0 {
    			activeWorker = worker			// 取出索引为0的值
    			activeValue = values[0]
    		}
    		/**
    		select 方式进行调度
    		        使用场景:比如有多个通道,但我打算是哪一个通道先给我数据,我就先执行谁
    		        这个select 可以是并行执行 channel管道
    		*/
    		select {
    		case n := <-c1:
    			// 将收到的数据存到values中
    			values = append(values, n)
    		case n := <-c2:
    			// 将收到的数据存到values中
    			values = append(values, n)
    		case activeWorker <- activeValue:
    			// 送出去后就需要把values中的第一个值拿掉
    			values = values[1:]
    		case <-tm:
    			fmt.Println("Bye")
    			return
    		}
    	}}
    登录后复制

    这里就是源码的实现,可以看到直接在select中是可以收到tm的值的,也就说如果到了10s,就会执行打印bye的操作。

    那么现在还有另外一个需求,就是如果在800毫秒的时间内还没有收到数据,可以做其它事情。

    使用举一反三的思想,你可以思考一下这件事情应该怎么做。

    添加这行代码

    其实也就很简单了,只需要在case中在设置一个定时器即可。

    既然说到了这里就在给大家补充一个用法tick := time.Tick(time.Second)

    同样也是在case中使用。

    tick

    这样就可以每秒来显示一下values队列有多少数据。

    这块的内容就结束了,最终给大家发一下源码,感兴趣的可以在自己的编辑器上试试看。

    package mainimport (
    	"fmt"
    	"math/rand"
    	"time")func worker(id int, c chan int) {
    	for n := range c {
    		// 手动让消耗速度变慢
    		time.Sleep(time.Second)
    		fmt.Printf("Worker %d receive %d\n", id, n)
    	}}func createWorker(id int) chan<- int {
    	c := make(chan int)
    	go worker(id, c)
    	return c}func generator() chan int {
    	out := make(chan int)
    	go func() {
    		i := 0
    		for {
    			// 随机睡眠1500毫秒以内
    			time.Sleep(
    				time.Duration(rand.Intn(1500)) *
    					time.Millisecond)
    			// 往out这个channel发送i值
    			out <- i
    			i++
    		}
    	}()
    	return out}func main() {
    	// 这里需要明白如果代码为var c1, c2 chan int  则c1和c2都为nil
    	// 在 select里面也是可以使用的,只不过是堵塞状态!
    	var c1, c2 = generator(), generator()
    	// 直接调用createWorker方法,返回的就是一个channel
    	var worker = createWorker(0)
    	// 用来收n的值
    	var values []int
    	// 返回的是一个channel
    	tm := time.After(10 * time.Second)
    	tick := time.Tick(time.Second)
    	for {
    		// 利用nil  channel的特性
    		var activeWorker chan<- int
    		var activeValue int
    		// 判断当values中有值时
    		if len(values) > 0 {
    			activeWorker = worker			// 取出索引为0的值
    			activeValue = values[0]
    		}
    		/**
    		select 方式进行调度
    		        使用场景:比如有多个通道,但我打算是哪一个通道先给我数据,我就先执行谁
    		        这个select 可以是并行执行 channel管道
    		*/
    		select {
    		case n := <-c1:
    			// 将收到的数据存到values中
    			values = append(values, n)
    		case n := <-c2:
    			// 将收到的数据存到values中
    			values = append(values, n)
    		case activeWorker <- activeValue:
    			// 送出去后就需要把values中的第一个值拿掉
    			values = values[1:]
    		case <-time.After(800 * time.Millisecond):
    			// 如果在800毫秒没有收到数据则提示超时
    			fmt.Println("timeout")
    		case <-tick:
    			// 每秒获取一下values中队列的长度
    			fmt.Println("queue len = ", len(values))
    		case <-tm:
    			fmt.Println("Bye")
    			return
    		}
    	}}
    登录后复制

    三、总结

    本文主要就是对于goroutine和channel的大量练习。

    文中的案例,有可能会一时半会理解不了,是没有关系的,不用钻牛角尖的。

    等你在go的海洋里遨游的时间长了,有些东西就自然而然的明白了。

    下一期的文章就是给大家实战一个go的并发爬虫项目。

    坚持学习、坚持写作、坚持分享是咔咔从业以来一直所秉持的信念。希望在偌大互联网中咔咔的文章能带给你一丝丝帮助。我是咔咔,下期见。

    以上是聊聊Go的并发编程 (二)的详细内容。更多信息请关注PHP中文网其他相关文章!

    本站声明
    本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

    热AI工具

    Undresser.AI Undress

    Undresser.AI Undress

    人工智能驱动的应用程序,用于创建逼真的裸体照片

    AI Clothes Remover

    AI Clothes Remover

    用于从照片中去除衣服的在线人工智能工具。

    Undress AI Tool

    Undress AI Tool

    免费脱衣服图片

    Clothoff.io

    Clothoff.io

    AI脱衣机

    Video Face Swap

    Video Face Swap

    使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

    热工具

    记事本++7.3.1

    记事本++7.3.1

    好用且免费的代码编辑器

    SublimeText3汉化版

    SublimeText3汉化版

    中文版,非常好用

    禅工作室 13.0.1

    禅工作室 13.0.1

    功能强大的PHP集成开发环境

    Dreamweaver CS6

    Dreamweaver CS6

    视觉化网页开发工具

    SublimeText3 Mac版

    SublimeText3 Mac版

    神级代码编辑软件(SublimeText3)

    Go WebSocket 消息如何发送? Go WebSocket 消息如何发送? Jun 03, 2024 pm 04:53 PM

    在Go中,可以使用gorilla/websocket包发送WebSocket消息。具体步骤:建立WebSocket连接。发送文本消息:调用WriteMessage(websocket.TextMessage,[]byte("消息"))。发送二进制消息:调用WriteMessage(websocket.BinaryMessage,[]byte{1,2,3})。

    深入理解 Golang 函数生命周期与变量作用域 深入理解 Golang 函数生命周期与变量作用域 Apr 19, 2024 am 11:42 AM

    在Go中,函数生命周期包括定义、加载、链接、初始化、调用和返回;变量作用域分为函数级和块级,函数内的变量在内部可见,而块内的变量仅在块内可见。

    如何在 Go 中使用正则表达式匹配时间戳? 如何在 Go 中使用正则表达式匹配时间戳? Jun 02, 2024 am 09:00 AM

    在Go中,可以使用正则表达式匹配时间戳:编译正则表达式字符串,例如用于匹配ISO8601时间戳的表达式:^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-][0-9]{2}:[0-9]{2})$。使用regexp.MatchString函数检查字符串是否与正则表达式匹配。

    Golang 与 Go 语言的区别 Golang 与 Go 语言的区别 May 31, 2024 pm 08:10 PM

    Go和Go语言是不同的实体,具有不同的特性。Go(又称Golang)以其并发性、编译速度快、内存管理和跨平台优点而闻名。Go语言的缺点包括生态系统不如其他语言丰富、语法更严格以及缺乏动态类型。

    Golang 技术性能优化中如何避免内存泄漏? Golang 技术性能优化中如何避免内存泄漏? Jun 04, 2024 pm 12:27 PM

    内存泄漏会导致Go程序内存不断增加,可通过:关闭不再使用的资源,如文件、网络连接和数据库连接。使用弱引用防止内存泄漏,当对象不再被强引用时将其作为垃圾回收目标。利用go协程,协程栈内存会在退出时自动释放,避免内存泄漏。

    如何在 IDE 中查看 Golang 函数文档? 如何在 IDE 中查看 Golang 函数文档? Apr 18, 2024 pm 03:06 PM

    使用IDE查看Go函数文档:将光标悬停在函数名称上。按下热键(GoLand:Ctrl+Q;VSCode:安装GoExtensionPack后,F1并选择"Go:ShowDocumentation")。

    Go 并发函数的单元测试指南 Go 并发函数的单元测试指南 May 03, 2024 am 10:54 AM

    对并发函数进行单元测试至关重要,因为这有助于确保其在并发环境中的正确行为。测试并发函数时必须考虑互斥、同步和隔离等基本原理。可以通过模拟、测试竞争条件和验证结果等方法对并发函数进行单元测试。

    Golang 函数接收 map 参数时的注意事项 Golang 函数接收 map 参数时的注意事项 Jun 04, 2024 am 10:31 AM

    在Go中传递map给函数时,默认会创建副本,对副本的修改不影响原map。如果需要修改原始map,可通过指针传递。空map需小心处理,因为技术上是nil指针,传递空map给期望非空map的函数会发生错误。

    See all articles