深入了解golang 的channel

2023-10-26

文章目录

1、channel 是什么

  • channel 我们也叫管道,顾名思义,他是用来传输某些东西的。
  • 没错,channel 主要用于 go中 不同gorouting 之间消息的传输

channel的特点

  1. channel 是 先进先出
  2. 并发安全

2、channel 的数据结构

hchan

type hchan struct {
	qcount   uint           // 队列中得所有数据数
	dataqsiz uint           // 环形队列的大小
	buf      unsafe.Pointer // 指向大小为 dataqsiz 的数组  。ring buf 数据结构,循环队列
	elemsize uint16			// 元素大小
	closed   uint32			// 是否关闭
	elemtype *_type 		// 元素类型
	sendx    uint   		// 发送索引
	recvx    uint   		// 接受索引
	recvq    waitq  		// recv 等待队列,即(<-chan)
	sendq    waitq  		// send 等待队列,即(ch<-)
	lock mutex				// lock 保护了 hchan 的所有字段,以及在此 channel 上阻塞的 sudog 的一些字段。当持有此锁时不改变其他 goroutine 的状态(不ready 的 goroutine),因为他会在栈收缩时发送死锁

在这里插入图片描述

等待队列和发送队列的类型包装 sudog

type sudog struct {
	g *g				// 由 sudog 阻塞的通道的hchan.lock 进行保护
	isSelect bool		// 表示g正在参与 一个select,因此 g.selectDone 必须已 CAS 的方式避免唤醒时候的 data race
	next *sudog			// 后指针		
	prev *sudog			// 前指针
	elem unsafe.Pointer // 数据元素,(可能指向栈)
	// 下面的字段永远不会被并发访问,只有 waitlink 会被 g 访问
	// 对于 semaphores,所有的字段(包括上面的)只会在持有 semaRoot 锁时被访问
	acquiretime int64
	releasetime int64
	ticket      uint32
	isSelect bool
	parent   *sudog // semaRoot 二叉树
	waitlink *sudog // g.waiting 列表 或 semaRoot
	waittail *sudog // semaRoot
	c        *hchan // channel
}

在这里插入图片描述

3、channel 分类

有缓冲channel

  • 创建时指定的缓冲大小大于等1
ch := make(chan int,5)
  • 有缓存的,发的时候不是必须有协程来接收,不会阻塞(接收者和发送者不需要同时就绪)

无缓冲channel

  • 创建channel 时指定的缓冲为0或者不指定
ch := make(chan int)
  • 无缓存,发的时候必须要有协程接收,否则会阻塞(接收者和发送者必须同时就绪)

4、channel 的创建

makechan interface{}4

4.1 分配 hchan

  • 找go 的运行时内存分配器来分配内存
  • 在堆中进行分配,所有字段均为零值
    在这里插入图片描述

4.2 分配buf

  • 根据指定的缓冲大小来分配缓冲,ring buf
    在这里插入图片描述
  • buf 是一个数组,但是内部处理的时候是根据 ring buf 的逻辑来处理的
  • buf 实际是 一个数组指针,该数组实际上是拼在这个hchan的内存后面,hchan和数组实际上是一段连续的内存
  • 创建无缓冲的channel 时,此处不做处理,不分配

4.3 makeChan 源码

func makechan64(t *chantype, size int64) *hchan {
	if int64(int(size)) != size {
		panic(plainError("makechan: size out of range"))
	}

	return makechan(t, int(size))
}

申请内存时控制传入的缓冲大小

func makechan(t *chantype, size int) *hchan {
	elem := t.elem

	// compiler checks this but be safe.
	// 编译器相关的检查
	if elem.size >= 1<<16 {
		throw("makechan: invalid channel element type")
	}
	if hchanSize%maxAlign != 0 || elem.align > maxAlign {
		throw("makechan: bad alignment")
	}
	// 计算内存大小,元素大小加上缓冲区,看是否溢出
	mem, overflow := math.MulUintptr(elem.size, uintptr(size))
	if overflow || mem > maxAlloc-hchanSize || size < 0 {
		panic(plainError("makechan: size out of range"))
	}

	// Hchan does not contain pointers interesting for GC when elements stored in buf do not contain pointers.
	// buf points into the same allocation, elemtype is persistent.
	// SudoG's are referenced from their owning thread so they can't be collected.
	// TODO(dvyukov,rlh): Rethink when collector can move allocated objects.
	var c *hchan
	switch {
	case mem == 0:
		// Queue or element size is zero.
		// 通过 mallocgc 申请内存,mallocgc 是 go的运行时内存分配器
		// 无缓冲的channel
		c = (*hchan)(mallocgc(hchanSize, nil, true))
		// Race detector uses this location for synchronization.
		c.buf = c.raceaddr()
	case elem.ptrdata == 0:
		// Elements do not contain pointers.
		// Allocate hchan and buf in one call.
		// hchanSize chan的大小加上 ring buf 的大小(mem)。连续内存
		// 元素不包含指针。指针这块是关于gc的处理
		c = (*hchan)(mallocgc(hchanSize+mem, nil, true))
		c.buf = add(unsafe.Pointer(c), hchanSize)
	default:
		// Elements contain pointers.
		// 元素包含指针
		c = new(hchan)
		c.buf = mallocgc(mem, elem, true)
	}
		// 初始化一些字段
	c.elemsize = uint16(elem.size)
	c.elemtype = elem
	c.dataqsiz = uint(size)	
	lockInit(&c.lock, lockRankHchan)

	if debugChan {
		print("makechan: chan=", c, "; elemsize=", elem.size, "; dataqsiz=", size, "\n")
	}
	return c
}

5、发送数据

ch <-1

在这里插入图片描述

5.1 对channel加锁

在这里插入图片描述

  • 锁住 ring buf ,保证数据安全

5.2 将数据拷贝到 buf

在这里插入图片描述

  • sendx 会加一,表示数据已发送

5.3 对channel 解锁

在这里插入图片描述

  • 有缓存的channel 是不阻塞的,解锁完成后 G1 就可以继续做其他的事情

6、接收数据

<-chan

6.1 先对channel上锁

在这里插入图片描述

6.2 从 buf 中拷出接收的数据

在这里插入图片描述

  • 接收数据的时候 recvx 会加一,表示数据已接收
  • sendxrecvx 数量相等时,表明 buf 中是无数据的

6.3 解锁

在这里插入图片描述

7、如果发送时,buf 数据已满?

7.1 将发送 goroutine 入队

在这里插入图片描述

  • gopark ,将G1从调度队列取出,进入阻塞状态,直到buf 有空位再唤醒

7.2 被唤醒时

7.2.1 send 出队

在这里插入图片描述

7.2.2 数据拷贝

在这里插入图片描述

  • 此处 6 直接被拷贝到了队首
  • 其他元素的位置是不用调整的,因为这是一个ring buf的处理逻辑

7.2.3 G1 重新加入调度队列

在这里插入图片描述

7.3 发送数据源码分析

func chansend1(c *hchan, elem unsafe.Pointer) {
	chansend(c, elem, true, getcallerpc())
}

本质还是调用了 chansend

// callerpc  运行时调试用的,不需太关注
func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
// fast path
	if c == nil {
		if !block {
			return false
		}
		// 向一个已经关闭的channel 发送数据,会死锁
		gopark(nil, nil, waitReasonChanSendNilChan, traceEvGoStop, 2)
		throw("unreachable")
	}

	if debugChan {
		print("chansend: chan=", c, "\n")
	}

	if raceenabled {
		racereadpc(c.raceaddr(), callerpc, funcPC(chansend))
	}

	if !block && c.closed == 0 && full(c) {
		return false
	}

	var t0 int64
	if blockprofilerate > 0 {
		t0 = cputicks()
	}
	// 持有锁
	lock(&c.lock)
	// 获取锁之后需要再检查是否被 close
	if c.closed != 0 {
		unlock(&c.lock)
		panic(plainError("send on closed channel"))
	}
	// 判断是否有正在 recvq 中 阻塞的接收协程,有的话直接进行数据的拷贝
	if sg := c.recvq.dequeue(); sg != nil {
		// Found a waiting receiver. We pass the value we want to send
		// directly to the receiver, bypassing the channel buffer (if any).
		send(c, sg, ep, func() { unlock(&c.lock) }, 3)
		return true
	}
	// 如果buf 中有剩余空间,直接将数据写入到 buf中
	if c.qcount < c.dataqsiz {
		// Space is available in the channel buffer. Enqueue the element to send.
		// 入队操作
		qp := chanbuf(c, c.sendx)
		if raceenabled {
			raceacquire(qp)
			racerelease(qp)
		}
		typedmemmove(c.elemtype, qp, ep)
		// 修改 sendx 
		c.sendx++
		if c.sendx == c.dataqsiz {
			c.sendx = 0
		}
		c.qcount++
		// 释放锁
		unlock(&c.lock)
		return true
	}

	if !block {
		unlock(&c.lock)
		return false
	}

	// Block on the channel. Some receiver will complete our operation for us.
	// 阻塞在 channel,等待接收者 接收数据
	gp := getg()
	mysg := acquireSudog()
	mysg.releasetime = 0
	if t0 != 0 {
		mysg.releasetime = -1
	}
	// No stack splits between assigning elem and enqueuing mysg
	// on gp.waiting where copystack can find it.
	mysg.elem = ep
	mysg.waitlink = nil
	mysg.g = gp
	mysg.isSelect = false
	mysg.c = c
	gp.waiting = mysg
	gp.param = nil
	// 放入到 sendq 中
	c.sendq.enqueue(mysg)
	// Signal to anyone trying to shrink our stack that we're about
	// to park on a channel. The window between when this G's status
	// changes and when we set gp.activeStackChans is not safe for
	// stack shrinking.
	atomic.Store8(&gp.parkingOnChan, 1)
	// park 结束之后 解锁,增加了回调
	gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanSend, traceEvGoBlockSend, 2)
	// Ensure the value being sent is kept alive until the
	// receiver copies it out. The sudog has a pointer to the
	// stack object, but sudogs aren't considered as roots of the
	// stack tracer.
	// 防止gc 清除要使用的数据
	KeepAlive(ep)

	// someone woke us up.
	if mysg != gp.waiting {
		throw("G waiting list is corrupted")
	}
	gp.waiting = nil
	gp.activeStackChans = false
	if gp.param == nil {
		if c.closed == 0 {
			throw("chansend: spurious wakeup")
		}
		panic(plainError("send on closed channel"))
	}
	gp.param = nil
	if mysg.releasetime > 0 {
		blockevent(mysg.releasetime-t0, 2)
	}
	mysg.c = nil
	releaseSudog(mysg)
	return true
}

8、如果接收时,buf 为空?

8.1 将接收 goroutine 入队

在这里插入图片描述

  • 入队后,G2 从调度队列取出,不再被 P 调度到

8.2 被唤醒时

8.2.1 出队

8.2.2 直接将数据 拷贝到 G2 中

在这里插入图片描述

  • G2 不再调度队列中,不存在竞争,写操作是安全的
  • 此处是一个优化过程,直接将发送的数据放入到接收数据的G2中.
  • 操作 ring buf 需要进行加锁,性能消耗比较大

8.2.3 G2重新加入到调度队列

  • 当G2 接收到数据后,就会重新加入到 P 的调度队列中,等待系统的调用,具体调用时间我们不关心

这种情况下的接收数据没有 buf 的参与,和无缓存的channel 类似

8.3 接收数据源码分析

func chanrecv1(c *hchan, elem unsafe.Pointer) {
	chanrecv(c, elem, true)
}

//go:nosplit
func chanrecv2(c *hchan, elem unsafe.Pointer) (received bool) {
	_, received = chanrecv(c, elem, true)
	return
}

一个处理了返回值,本质还是调用了通用的 chanrecv

func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {
	// raceenabled: don't need to check ep, as it is always on the stack
	// or is new memory allocated by reflect.

	if debugChan {
		print("chanrecv: chan=", c, "\n")
	}

	if c == nil {
		if !block {
			return
		}
		gopark(nil, nil, waitReasonChanReceiveNilChan, traceEvGoStop, 2)
		throw("unreachable")
	}

	// Fast path: check for failed non-blocking operation without acquiring the lock.
	// 非阻塞情况下,如果失败,直接返回,不需要持有锁
	if !block && empty(c) {
		if atomic.Load(&c.closed) == 0 {
			return
		}
		if empty(c) {
			// The channel is irreversibly closed and empty.
			if raceenabled {
				raceacquire(c.raceaddr())
			}
			if ep != nil {
				typedmemclr(c.elemtype, ep)
			}
			return true, false
		}
	}

	var t0 int64
	if blockprofilerate > 0 {
		t0 = cputicks()
	}
	// 持有锁
	lock(&c.lock)

	if c.closed != 0 && c.qcount == 0 {
		if raceenabled {
			raceacquire(c.raceaddr())
		}
		unlock(&c.lock)
		if ep != nil {
			typedmemclr(c.elemtype, ep)
		}
		return true, false
	}
	// 看看有没有发送方阻塞在这上面。如果有的话,直接向这个goroutine 拿数据即可,不需要经过 ring buf
	if sg := c.sendq.dequeue(); sg != nil {
		recv(c, sg, ep, func() { unlock(&c.lock) }, 3)
		return true, true
	}
	// 如果buf 中海油数据,直接从buf 中拿
	if c.qcount > 0 {
		// Receive directly from queue
		qp := chanbuf(c, c.recvx)
		if raceenabled {
			raceacquire(qp)
			racerelease(qp)
		}
		if ep != nil {
			// 拿出数据
			typedmemmove(c.elemtype, ep, qp)
		}
		// 清理数据
		typedmemclr(c.elemtype, qp)
		// 增加索引
		c.recvx++
		if c.recvx == c.dataqsiz {
			c.recvx = 0
		}
		c.qcount--
		unlock(&c.lock)
		return true, true
	}

	if !block {
		unlock(&c.lock)
		return false, false
	}

	// no sender available: block on this channel.
	// 如果接收的时候buf 为空,且没人发数据,就入队 recvq,阻塞等待被唤醒
	gp := getg()
	mysg := acquireSudog()
	mysg.releasetime = 0
	if t0 != 0 {
		mysg.releasetime = -1
	}
	// No stack splits between assigning elem and enqueuing mysg
	// on gp.waiting where copystack can find it.
	mysg.elem = ep
	mysg.waitlink = nil
	gp.waiting = mysg
	mysg.g = gp
	mysg.isSelect = false
	mysg.c = c
	gp.param = nil
	c.recvq.enqueue(mysg)
	atomic.Store8(&gp.parkingOnChan, 1)
	gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanReceive, traceEvGoBlockRecv, 2)

	// someone woke us up
	if mysg != gp.waiting {
		throw("G waiting list is corrupted")
	}
	gp.waiting = nil
	gp.activeStackChans = false
	if mysg.releasetime > 0 {
		blockevent(mysg.releasetime-t0, 2)
	}
	closed := gp.param == nil
	gp.param = nil
	mysg.c = nil
	releaseSudog(mysg)
	return true, !closed
}

9、关闭channel

  1. 加锁
  2. closed 标志位置为1
  3. 将阻塞的 sendq 和 recvq 中得 G 临时保存到 glist 中。等待操作系统调度
  4. 解锁
    在这里插入图片描述
  • 需要尽快释放锁,因为 此时buf 可能部位空,其他协程需要拿数据,如果一直加锁的话会 影响其他协程

10、读取一个已经关闭的channel

在这里插入图片描述

  • 因为关闭的时候,已经将sendq 和 recvq 中得g全部存到 glist 中,所以此时的 sendq 和 recvq 一定是 nil 的
  • buf 中可能还有数据,有数据的话就正常读取
  • buf 没有数据的话,会返回 零值

11、关闭channel 源码分析

func closechan(c *hchan) {
	// 不可关闭已关闭的channel
	if c == nil {
		panic(plainError("close of nil channel"))
	}
	// 获取锁
	lock(&c.lock)
	// 再次判断channel 是否被关闭
	if c.closed != 0 {
		unlock(&c.lock)
		panic(plainError("close of closed channel"))
	}

	if raceenabled {
		callerpc := getcallerpc()
		racewritepc(c.raceaddr(), callerpc, funcPC(closechan))
		racerelease(c.raceaddr())
	}
	// 关闭 channel
	c.closed = 1

	var glist gList
	// 将 sendq 和 recvq 中所有的g 都临时存放在 gList中
	// release all readers
	for {
		sg := c.recvq.dequeue()
		if sg == nil {
			break
		}
		if sg.elem != nil {
			typedmemclr(c.elemtype, sg.elem)
			sg.elem = nil
		}
		if sg.releasetime != 0 {
			sg.releasetime = cputicks()
		}
		gp := sg.g
		gp.param = nil
		if raceenabled {
			raceacquireg(gp, c.raceaddr())
		}
		glist.push(gp)
	}

	// release all writers (they will panic)
	for {
		sg := c.sendq.dequeue()
		if sg == nil {
			break
		}
		sg.elem = nil
		if sg.releasetime != 0 {
			sg.releasetime = cputicks()
		}
		gp := sg.g
		gp.param = nil
		if raceenabled {
			raceacquireg(gp, c.raceaddr())
		}
		glist.push(gp)
	}
	// 将所有的G都存放在gList 中时,就可释放锁
	// 拿着锁去read 比较费性能
	unlock(&c.lock)

	// Ready all Gs now that we've dropped the channel lock.
	// 依次将 所有的g 就绪
	for !glist.empty() {
		gp := glist.pop()
		gp.schedlink = 0
		goready(gp, 3)
	}
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

深入了解golang 的channel 的相关文章

  • Go基础(复杂类型):函数的闭包

    函数的闭包 Go 函数可以是一个闭包 闭包是一个函数值 它引用了其函数体之外的变量 该函数可以访问并赋予其引用的变量的值 换句话说 该函数被 绑定 在了这些变量上 例如 函数 adder 返回一个闭包 每个闭包都被绑定在其各自的 sum 变
  • 一个简单的Golang实现的HTTP Proxy方法

    最近因为换了Mac 以前的Linux基本上不再使用了 但是我的SS代理还得用 SS代理大家都了解 一个很NB的socks代理工具 但是就是因为他是Socks的 想用HTTP代理的时候很不方便 以前在Linux下的时候 会安装一个Privox
  • go Hijack

    golang GO语言 http详解简单基础 1 因为好像长时间的写PHP可能大家感觉烦躁了 所以写一点golang的东西大家可以拿去玩玩 golang在web开发中让你得心应手 其实也是很好的东西 只要你玩进去之后感觉好爽 感觉比PHP的
  • fabric中链码关于缺少go的包依赖问题的解决方法

    我们可以在cmd中下载依赖包 go get github com hyperledger fabric common util v1 4 go get github com hyperledger fabric core chaincode
  • 用Go实现的简易TCP通信框架

    接触到GO之后 GO的网络支持非常令人喜欢 GO实现了在语法层面上可以保持同步语义 但是却又没有牺牲太多性能 底层一样使用了IO路径复用 比如在LINUX下用了EPOLL 在WINDOWS下用了IOCP 但是在开发服务端程序的时候 很多都是
  • 微信小程序总结(2)- 需求分析

    在真正进入代码开发之前 很重要的一步就是进行需求分析 用户画像 这款微信小程序的主要用户是谁 是年轻人 中年人 还是老年人 是男生 还是女生 是工薪阶层 还是企业主 是金融理财 还是在线票务 在进行一定范围的样本调查后 可以得出一个精准的用
  • Go编程技巧--io.Reader/Writer

    Go原生的pkg中有一些核心的interface 其中io Reader Writer是比较常用的接口 很多原生的结构都围绕这个系列的接口展开 在实际的开发过程中 你会发现通过这个接口可以在多种不同的io类型之间进行过渡和转化 本文结合实际
  • 开源Go语言数值算法库 An open numerical library purely based on Go programming language

    目录 关于 goNum https github com chfenger goNum 安装环境 安装方法 算法 许可证书 致谢 关于goNum goNum是一款完全以Go语言为基础的开源数值算法库 它可以使你像调用其它go函数一样使用其进
  • 【笔记】Go语言学习笔记

    一 概述 什么是程序 程序 为了让计算机执行某些操作或解决某个问题而编写的一系列有序指令的集合 Go语言 是区块链最主流的编程语言 同时也是当前最具发展潜力的语言 Go语言是Google公司创造的语言 也是Google主推的语言 Googl
  • fabric2.X以上系统用test-network环境测试自己的链码

    首先 我们需要安装好fabric2 X的环境 具体参考我之前的文章 这里默认已经有了fabric2 X的环境 进入test network文件夹 在开始测试之前 先把gopath项目路径全部解锁 sudo chmod R 777 GOPAT
  • go语言学习笔记1--flag代码包

    flag代码包用于接收和解析命令参数 我们以hello world代码作为示例 package main import fmt func main fmt Println hello world 现在 我们想要根据输入定制hello的对象
  • [GO语言基础] 三.变量声明、数据类型、标识符及编程练习12题

    作为网络安全初学者 会遇到采用Go语言开发的恶意样本 因此从今天开始从零讲解Golang编程语言 一方面是督促自己不断前行且学习新知识 另一方面是分享与读者 希望大家一起进步 前文介绍了Go的编译运行 语法规范 注释转义及API标准库知识
  • Go-OpenWrt获取wan口ip、dns、网关ip

    Go OpenWrt获取wan口ip dns 网关ip 文章目录 Go OpenWrt获取wan口ip dns 网关ip 1 前言 2 解决方案思路 3 代码 1 前言 一般来说 Openwrt可以配置多个wan口和多个lan口 这里获取的
  • 【Go语言教程】(一) 下载、安装、配置

    1 下载 官网安装包下载地址为 https golang org dl 如果打不开可以使用这个地址 https golang google cn dl 找到适合你系统的版本下载 我下载的是windows版本 2 安装 msi文件点击完成安装
  • Gin的使用

    Gin框架 gin框架路由使用前缀树 路由注册的过程是构造前缀树的过程 路由匹配的过程就是查找前缀树的过程 gin框架的中间件函数和处理函数是以切片形式的调用链条存在的 我们可以顺序调用也可以借助c Next 方法实现嵌套调用 借助c Se
  • Go Web编程实战(2)----流程控制语句

    目录 流程控制语句 if else语句 for循环语句 用for循环实现do while 用for循环实现while break指定跳出循环 continue语句 for range循环 遍历数组 遍历字符串 遍历map 遍历通道 chan
  • Go语言的跨文件调包

    一 前言 文件的结构如下 go mod main main go util util go 文件在调用其它包的时候 需要在代码中引用其他的函数 包的命名一般都以小写为主 文件中函数的以首字符为大写的函数为共有函数 小写的为私有函数 只能是在
  • Go语言学习-基本

    命名 如果是在函数外部定义 那么将在当前包的所有文件中都可以访问 名字的开头字母的大小写决定了名字在包外的可见性 如果一个名字是大写字母开头的 译注 必须是在函数外部定义的包级名字 包级函数名本身也是包级名字 那么它将是导出的 也就是说可以
  • 使用Go Hijack和jQuery轻松实现异步推送服务

    使用Go Hijack和jQuery轻松实现异步推送服务 首先要说明的是 这里实现的异步推送服务采用的是Long Polling方式 并不是Comet 如果想用Comet来实现的话 可以参考这个开源项目 http cometd org 不过
  • go语言连接mysql数据库,并验证连通性

    go语言连接mysql数据库 并验证连通性 package main import database sql sql Open加载包 github com go sql driver mysql 没用到包里的内容但是需要加载一下这个包 lo

随机推荐

  • 网络营销中的博客营销有什么价值?对于博客营销我们应该注意哪些问题?

    博客营销有什么价值 应该注意什么 博客营销的正向效应作为一种现代 新兴的互联网营销手段 博客营销具有着传统营销方式所不可比拟的价值 主要体现在其正向的外部性方面 第一 由于网络资源的共享性和交互性 博客营销有利于企业全面降低营销费用和经营成
  • 最全防雷器电路及保护电路解析

    一 交流电源防雷器 一 单相并联式防雷器 电路一 最简单的电路 说明 1 优点 电路简单 采用复合对称电路 共模 差模全保护 L N 可以随便接 缺点 压敏电阻RV1 短路失效后易引起火灾 最好在每个压敏电阻上串联一个工频保险丝以防压敏电阻
  • 虚拟linux系统首次登入,第一次在虚拟机启动我们的Linux系统

    在上一篇中 我们学习了如何在本地环境搭建自己的Linux学习环境 从今天开始 正式进入我们的Linux学习之旅 今天我们尝试第一次启动Linux系统 为保证虚拟机的流畅运行 建议将分辨率设置为 800x600 1 第一次启动Linux之后
  • SSD HDD RAM ROM NAND NOR Flash 等存储介质概念整理

    原文地址 http xyzhongly blog 163 com blog static 2820488820101022302695 fromdm fromSearch isFromSearchEngine yes 寄存器 寄存器是中央处
  • C# 发展史

    C 开发的进化史 今天开始呢 我会跟大家一起学习C 我会由浅入深从基础来教大家 给大家推荐一本个人认为最为适合C 入门的书 C 本质论 这本书写的确实不错 如果有兴趣的话可以买来看一看 在期刊期间我也会发一些关于有趣的小案例 努力让大家学到
  • 区块链应用的开发

    经过前面两篇文章 适合小白 区块链之我用可视化的方式部署Webase 区块链之我用可视化Webase开发智能合约 的洗礼 相信大家都对区块链这块多少有点了解了 在本章节小编将带大家演示一下区块链应用的开发 首先需要导出刚才编译部署的智能合约
  • 毕设(一):正则化极限学习机(RELM)、在线学习的极限学习机(OS-ELM)、带遗忘机制的在线学习极限学习机(FOS-ELM)

    前言 终于要毕业了 毕业设计也做完了 我的毕设是 极限学习机和强化学习在单一资产交易中的应用 本质上用以极限学习机为值函数逼近器的一类强化学习算法去对一个资产进行交易 既然毕设也做完了 大学生涯也要结束了 那在去工作之前将毕设的东西好好总结
  • 线性回归(Linear Regression)

    线性回归 Linear Regression 一 假设函数 h x
  • Linux 强行终止

    kill 9 pid pid是进程号 9 代表的是数字 INT 2 这个就是你在bash下面用Ctrl C 来结束一个程序时 bash会向进程发送这个信号 默认的 进程收到这个程序会结束 你可以用 kill INT pid 来发这个信号 Q
  • ORA-28547 连接服务器失败

    1 找到Oracle安装路径 找到Oracle安装路径 app product 11 2 0 dbhome 1 NETWORK ADMIN listener ora 2 在listener ora文件中找到 PROGRAM extproc
  • OpenAI使用条款、使用策略和支持的地区汇总:必读指南,避免OpenAI API被封禁

    最近 一些群友反馈他们的OpenAI API被限制 其中包括试用金用户以及绑定了信用卡的用户 当他们调用API时 会收到以下报错信息 Your access was terminated due to violation of our po
  • 第一章:认识Scratch 第一课 什么是编程,什么是计算机语言?

    程序员的高薪已经成为一个公开的秘密 北上广的一个普通的刚毕业的程序员 怎么说也要万元的起薪 工作几年之后 说起来月薪都是几万 那些高级的资深程序员甚至于达到了年薪百万的待遇 程序员的工作就是编程 那么到底什么是编程呢 关注公众号 少儿编程S
  • python3.6安装包下载_下载 - CPython v3.8.5 官方安装包,离线安装程序,绿色便携版

    CPython v3 8 5 官方安装包 for Digitser 基于 C 语言的 Python 实现 系统 Microsoft Windows Vista 7 8 10 x86 amd64 CPython2 7 原定于 2020 年 0
  • android cmd命令行删除文件夹,文件

    android cmd命令行删除文件夹 文件 adb root adb remount adb shell su cd system sd data 进入系统内指定文件夹 ls 列表显示当前文件夹内容 rm r xxx 删除名字为xxx的文
  • Angular Tracy 小笔记 数据绑定,指令

    数据绑定 数据绑定的本质 就是我们的通讯操作 左边的业务逻辑 ts 想传递数据给模板显示 html 可以通过 插件表达式 data 属性绑定 property data 插值表达式 data 变量调用 html 里写 p tracyName
  • hyper-v克隆win10虚拟机后无法联网的解决方案

    克隆的虚拟机mac地址是不变的 所以要修改mac地址才行 现在有个更简单的办法 就是直接删除网络适配器 然后重新添加一个网络适配器即可 第一步 先删除原来的网卡 第二步 添加新的网卡 然后确定保存 立即生效
  • vue3.2结合element-plus实现一个全局分页组件

    最近开始学习vue3 0的api语法 通使用vue3 0 element plus搭建一个模板 把常用的组件封装一下 常用的分页组件 通过封装之后 粘贴复制 开箱即用 首先安装vue3 2版本和element plus 分页组件
  • Python-OpenCv-答题卡识别

    前言 用OpenCv进行答题卡的扫描获取信息 其中用到平滑处理 边缘检测 透视变换 坐标点处理 一 轮廓检测 import cv2 import numpy as np def cv show name img cv2 imshow nam
  • 在linux-CentOS7.9中搭建DHCP服务器

    目录 dhcp协议 dhcp分配的过程 在linux系统里搭建一个dhcp服务 给其他机器分配ip地址 具体步骤 1 安装dhcp相关的软件包 2 拷贝样例文件到 etc dhcp目录下 3 编辑配置文件 4 启动dhcp服务器 5 查看d
  • 深入了解golang 的channel

    文章目录 1 channel 是什么 channel的特点 2 channel 的数据结构 hchan 等待队列和发送队列的类型包装 sudog 3 channel 分类 有缓冲channel 无缓冲channel 4 channel 的创