Go-并发编程基础(goroutine、channel、select等)_goroutine select

本人从事网路安全工作12年,曾在2个大厂工作过,安全服务、售后服务、售前、攻防比赛、安全讲师、销售经理等职位都做过,对这个行业了解比较全面。

最近遍览了各种网络安全类的文章,内容参差不齐,其中不伐有大佬倾力教学,也有各种不良机构浑水摸鱼,在收到几条私信,发现大家对一套完整的系统的网络安全从学习路线到学习资料,甚至是工具有着不小的需求。

最后,我将这部分内容融会贯通成了一套282G的网络安全资料包,所有类目条理清晰,知识点层层递进,需要的小伙伴可以点击下方小卡片领取哦!下面就开始进入正题,如何从一个萌新一步一步进入网络安全行业。

学习路线图

其中最为瞩目也是最为基础的就是网络安全学习路线图,这里我给大家分享一份打磨了3个月,已经更新到4.0版本的网络安全学习路线图。

相比起繁琐的文字,还是生动的视频教程更加适合零基础的同学们学习,这里也是整理了一份与上述学习路线一一对应的网络安全视频教程。

网络安全工具箱

当然,当你入门之后,仅仅是视频教程已经不能满足你的需求了,你肯定需要学习各种工具的使用以及大量的实战项目,这里也分享一份我自己整理的网络安全入门工具以及使用教程和实战。

项目实战

最后就是项目实战,这里带来的是SRC资料&HW资料,毕竟实战是检验真理的唯一标准嘛~

面试题

归根结底,我们的最终目的都是为了就业,所以这份结合了多位朋友的亲身经验打磨的面试题合集你绝对不能错过!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以点击这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 并行:指同一时刻能运行多个指令。
  • 进程:一段程序的执行过程,是系统进行资源分配的基本单位,一个进程至少有一个线程
  • 线程:操作系统能够进行运算调度的最小单位,它被包含在进程之中。

协程 goroutine

  • 有独立的栈空间
  • 共享程序堆空间
  • 调度由用户控制
  • 主线程是一个物理线程,直接作用在cpu上的,是重量级的,非常耗费cpu资源,
  • 协程从主线程开启的,是轻量级的线程,是**逻辑态,**对资源消耗相对小。
  • Golang的协程机制是重要的特点,可以轻松的开启上万个协程。其它编程语言的并发机制是一般基于线程的,开启过多的线程,资源耗费大,这里就突显Golang在并发上的优势了

当一个程序启动时,其主函数即在一个单独的goroutine中运行,称之为main goroutine。新的goroutine会用go语句来创建。在语法上,go语句是一个普通的函数或方法调用前加上关键字go。go语句会使其语句中的函数在一个新创建的goroutine中运行。而go语句本身会迅速地完成。当主函数返回时,所有的goroutine都会直接打断,程序退出。

操作已经就绪,对应的goroutine就会重新分配到逻辑处理器上来完成操作。调度器对可以创建的逻辑处理器的数量没有限制,但语言运行时默认限制每个程序最多创建10000个线程。这个限制值可以通过调用runtime/debug包的SetMaxThreads方法来更改。如果程序试图使用更多的线程,就会崩溃。

goroutine调度-MPG模式

M(Main Thread):操作系统的主线程(是物理线程),又称内核线程。

P(Processor):处理器,管理协程,例如协程执行需要的上下文等

G(Goroutine):go协程

举个例子:

分成两个部分来看

原来的情况是M主线程正在执行G0协程,另外有三个协程在队列等待如果G0协程阻塞,比如读取文件或者数据库等。

这时就会创建M1主线程(也可能是从已有的线程池中取出M1),并且将等待的3个协程挂到M1下开始执行,M0的主线程下的G0仍然执行文件io的读写。等到G0不阻塞了,M0会被放到空闲的主线程继续执行(从已有的线程池中取),同时G0又会被唤醒。

这样的MPG调度模式,可以既让G0执行,同时也不会让队列的其它协程一直阻塞,仍然可以并发/并行执行。

进行调度的调度器为Seched,它维护有存储空闲的M队列和空闲的P队列,可运行的G队列,自由的G队列以及调度器的一些状态信息等。

package main

import (
	"fmt"
	"runtime"
	"time"
)

func routinetest(name string){
	for i:=0;i<3;i++{
		fmt.Println("routinetest",i,":hello,",name)
		time.Sleep(100*time.Millisecond)
	}
}

func main() {
	num :=runtime.NumCPU()
	fmt.Println(num)
	runtime.GOMAXPROCS(num)
	//runtime.GOMAXPROCS(1)
	//--------使用go开启协程----------
	go routinetest("lady")
	go routinetest("killer")
	time.Sleep(time.Second)
}

可以通过runtime.GOMAXPROCS设置cpu数,这里设置成了8个。

通道Channel

相对于sync的低水平同步,使用channel可以实现高水平同步,channel是先进先出的。

数据结构

Channel 在运行时的内部表示是 runtime.hchan,该结构体中包含了用于保护成员变量的互斥锁,从某种程度上说,Channel 是一个用于同步和通信的有锁队列,使用互斥锁解决程序中可能存在的线程竞争问题是很常见的,我们能很容易地实现有锁队列。

type hchan struct {
	qcount   uint
	dataqsiz uint
	buf      unsafe.Pointer
	elemsize uint16
	closed   uint32
	elemtype *_type
	sendx    uint
	recvx    uint
	recvq    waitq
	sendq    waitq
	lock mutex
}

runtime.hchan 结构体中的五个字段 qcountdataqsizbufsendxrecv 构建底层的循环队列:

  • qcount — Channel 中的元素个数;
  • dataqsiz — Channel 中的循环队列的长度;
  • buf — Channel 的缓冲区数据指针;
  • sendx — Channel 的发送操作处理到的位置;
  • recvx — Channel 的接收操作处理到的位置;

除此之外,elemsize 和 elemtype 分别表示当前 Channel 能够收发的元素类型和大小;sendq 和 recvq 存储了当前 Channel 由于缓冲区空间不足而阻塞的 Goroutine 列表,这些等待队列使用双向链表 runtime.waitq 表示,链表中所有的元素都是 runtime.sudog 结构:

type waitq struct {
	first *sudog
	last  *sudog
}

runtime.sudog 表示一个在等待列表中的 Goroutine,该结构中存储了两个分别指向前后 runtime.sudog 的指针以构成链表。

声明&初始化

初始化需要使用make(t Type, size …IntegerType) Type,size为缓存大小

var b chan int
var c = make(chan int)
var d = make(chan int,10)

b为nil,c为无缓存channel,d为有缓存channel

发送与接收

发送使用channel<-data,接收使用[var,ok]:=<-channel,当左侧没有变量接收时会直接丢弃掉数据,ok可以标识channel是否有数据,无数据是,接收变量获取到的是对应类型的零值。

对于nil的channel,发送和接收都会阻塞,所以不make的channel没有用,实际编程中channel应该都初始化

对于无缓存的channel,发送后会阻塞,直至接收

对于有缓存的channel,满了后发送会被阻塞,接收无影响

遍历和关闭

close

关闭后无法写入,只能读取,例如

close(d)

普通for循环

	for j := 0;j<len(c); j++{
		fmt.Println(<-c)
	}

若取的时候,没有其他goroutine写入的话,会读出一半。例如,刚开始len©是10个,当j为5时,len©也是5了,就跳出循环了。

	for j := 0;len(c)!=0; j++{
		fmt.Println(<-c)
	}

上面这种方法可以

for range

关闭后可以正常遍历,遍历也是从channel中接收值,大小会变化,例如

	for data := range d{
		fmt.Println(data)
	}

若不关闭,会一直接收数据,即使当前channel没有数据了,无goroutine写入时会block,若是在main routine中,会导致deadlock错误。

channel状态总结

动作\状态nil非空空的满了没满
接收阻塞接收值阻塞接收值接收值
发送阻塞发送值发送值阻塞发送值
关闭panic关闭成功,读完数据后返回零值关闭成功,返回零值关闭成功,读完数据后返回零值关闭成功,读完数据后返回零值

单方向的channel

只发送chan<-int

只接收

var in =  make(chan <- int)
var out = make(<-chan int,3)

channel中的channel

package main

import "fmt"

type Request struct{
	num int
	result chan int
}

func result(r Request)  {
	r.result <- r.num + 1
}

func main() {
	r := Request{1,make(chan int)}
	go result(r)
	fmt.Println(<-r.result)
}

常见错误

panic: close of nil channel

写在最后

在结束之际,我想重申的是,学习并非如攀登险峻高峰,而是如滴水穿石般的持久累积。尤其当我们步入工作岗位之后,持之以恒的学习变得愈发不易,如同在茫茫大海中独自划舟,稍有松懈便可能被巨浪吞噬。然而,对于我们程序员而言,学习是生存之本,是我们在激烈市场竞争中立于不败之地的关键。一旦停止学习,我们便如同逆水行舟,不进则退,终将被时代的洪流所淘汰。因此,不断汲取新知识,不仅是对自己的提升,更是对自己的一份珍贵投资。让我们不断磨砺自己,与时代共同进步,书写属于我们的辉煌篇章。

需要完整版PDF学习资源私我

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以点击这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值