当前位置: 首页 > news >正文

context.WithCancel()的使用

WithCancel可以将一个Context包装为cancelCtx,并提供一个取消函数,调用这个取消函数,可以Cancel对应的Context

Go语言context包-cancelCtx[1]


疑问


context.WithCancel()取消机制的理解[2]

父母5s钟后出门,倒计时,父母在时要学习,父母一走就可以玩

package main

import (
 "context"
 "fmt"
 "time"
)

func dosomething(ctx context.Context) {
 for {
  select {
  case <-ctx.Done():
   fmt.Println("playing")
   return
  default:
   fmt.Println("I am working!")
   time.Sleep(time.Second)
  }
 }
}

func main() {
 ctx, cancelFunc := context.WithCancel(context.Background())
 go func() {
  time.Sleep(5 * time.Second)
  cancelFunc()
 }()
 dosomething(ctx)
}
alt

为什么调用cancelFunc就能从ctx.Done()里取得返回值? 进而取消对应的Context?


复习一下channel的一个特性


从一个已经关闭的channel里可以一直获取对应的零值

alt

WithCancel代码分析


pkg.go.dev/context#WithCancel:[3]

// WithCancel returns a copy of parent with a new Done channel. The returned
// context's Done channel is closed when the returned cancel function is called
// or when the parent context's Done channel is closed, whichever happens first.
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete.

//WithCancel 返回具有新 Done 通道的 parent 副本。 返回的上下文的完成通道在调用返回的取消函数或父上下文的完成通道关闭时关闭,以先发生者为准。

//取消此上下文会释放与其关联的资源,因此代码应在此上下文中运行的操作完成后立即调用取消。

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
 if parent == nil {
  panic("cannot create context from nil parent")
 }
 c := newCancelCtx(parent)   // 将parent作为父节点context 生成一个新的子节点

 //获得“父Ctx路径”中可被取消的Ctx
 //将child canceler加入该父Ctx的map中
 propagateCancel(parent, &c)
 return &c, func() { c.cancel(true, Canceled) }
}

WithCancel最后返回 子上下文和一个cancelFunc函数,而cancelFunc函数里调用了cancelCtx这个结构体的方法cancel

(代码基于go 1.16; 1.17有所改动)

// A cancelCtx can be canceled. When canceled, it also cancels any children
// that implement canceler.
type cancelCtx struct {
 Context

 mu       sync.Mutex            // protects following fields
 done     chan struct{}         // created lazily, closed by first cancel call done是一个channel,用来 传递关闭信号
 children map[canceler]struct{} // set to nil by the first cancel call  children是一个map,存储了当前context节点下的子节点
 err      error                 // set to non-nil by the first cancel call  err用于存储错误信息 表示任务结束的原因
}

在cancelCtx这个结构体中,字段done是一个传递空结构体类型的channel,用来在上下文取消时关闭这个通道,err就是在上下文被取消时告诉用户这个上下文取消了,可以用ctx.Err()来获取信息

canceler是一个实现接口,用于Ctx的终止。实现该接口的Context有cancelCtx和timerCtx,而emptyCtx和valueCtx没有实现该接口。

alt
// A canceler is a context type that can be canceled directly. The
// implementations are *cancelCtx and *timerCtx.
type canceler interface {
 cancel(removeFromParent bool, err error)
 Done() <-chan struct{}
}

// closedchan is a reusable closed channel.
var closedchan = make(chan struct{})

func init() {
 close(closedchan)
}

// cancel closes c.done, cancels each of c's children, and, if
// removeFromParent is true, removes c from its parent's children.
/**
* 1、cancel(...)当前Ctx的子节点
* 2、从父节点中移除该Ctx
**/

func (c *cancelCtx) cancel(removeFromParent bool, err error) {
 if err == nil {
  panic("context: internal error: missing cancel error")
 }
 c.mu.Lock()
 if c.err != nil {
  c.mu.Unlock()
  return // already canceled
 }
 // 设置取消原因
 c.err = err

 //  设置一个关闭的channel或者将done channel关闭,用以发送关闭信号
 if c.done == nil {
  c.done = closedchan
 } else {
  close(c.done) // 注意这一步
 }

  // 将子节点context依次取消
 for child := range c.children {
  // NOTE: acquiring the child's lock while holding parent's lock.
  child.cancel(false, err)
 }
 c.children = nil
 c.mu.Unlock()

 if removeFromParent {
   // 将当前context节点从父节点上移除
  removeChild(c.Context, c)
 }
}

对于cancel函数,其取消了基于该上下文的所有子上下文以及把自身从父上下文中取消

对于更多removeFromParent代码分析,和其他Context的使用,强烈建议阅读 深入理解Golang之Context(可用于实现超时机制)[4]


 // Done is provided for use in select statements:
 //
 //  // Stream generates values with DoSomething and sends them to out
 //  // until DoSomething returns an error or ctx.Done is closed.
 //  func Stream(ctx context.Context, out chan<- Value) error {
 //   for {
 //    v, err := DoSomething(ctx)
 //    if err != nil {
 //     return err
 //    }
 //    select {
 //    case <-ctx.Done():
 //     return ctx.Err()
 //    case out <- v:
 //    }
 //   }
 //  }
 //
 // See https://blog.golang.org/pipelines for more examples of how to use
 // a Done channel for cancellation.
 Done() <-chan struct{}

 // If Done is not yet closed, Err returns nil.
 // If Done is closed, Err returns a non-nil error explaining why:
 // Canceled if the context was canceled
 // or DeadlineExceeded if the context's deadline passed.
 // After Err returns a non-nil error, successive calls to Err return the same error.
 Err() error

当调用cancelFunc()时,会有一步close(d)的操作,

ctx.Done 获取一个只读的 channel,类型为结构体。可用于监听当前 channel 是否已经被关闭。

Done()用来监听cancel操作(对于cancelCtx)或超时操作(对于timerCtx),当执行取消操作或超时时,c.done会被close,这样就能从一个已经关闭的channel里一直获取对应的零值<-ctx.Done便不会再阻塞

(代码基于go 1.16; 1.17有所改动)

func (c *cancelCtx) Done() <-chan struct{} {
 c.mu.Lock()
 if c.done == nil {
  c.done = make(chan struct{})
 }
 d := c.done
 c.mu.Unlock()
 return d
}

func (c *cancelCtx) Err() error {
 c.mu.Lock()
 err := c.err
 c.mu.Unlock()
 return err
}

总结一下:使用context.WithCancel时,除了返回一个新的context.Context(上下文),还会返回一个cancelFunc。 在需要取消该context.Context时,就调用这个cancelFunc,之后当前上下文及其子上下文都会被取消,所有的 Goroutine 都会同步收到这一取消信号

至于cancelFunc是如何做到的?

在用户代码,for循环里select不断尝试从 <-ctx.Done()里读取出内容,但此时并没有任何给 c.done这个channel写入数据的操作,(类似c.done <- struct{}{}),故而在for循环里每次select时,这个case都不满足条件,一直阻塞着。每次都执行default代码段

而在执行cancelFunc时, 在func (c *cancelCtx) cancel(removeFromParent bool, err error)里面,会有一个close(c.done)的操作。而从一个已经关闭的channel里可以一直获取对应的零值,即 select可以命中,进入case res := <-ctx.Done():代码段


可用如下代码验证:

package main

import (
 "context"
 "fmt"
 "time"
)

func dosomething(ctx context.Context) {

 var cuiChan = make(chan struct{})

 go func() {
  cuiChan <- struct{}{}
 }()

 //close(cuiChan)

 for {
  select {
  case res := <-ctx.Done():
   fmt.Println("res:", res)
   return
  case res2 := <-cuiChan:
   fmt.Println("res2:", res2)
  default:
   fmt.Println("I am working!")
   time.Sleep(time.Second)
  }
 }
}

func main() {

 test()
 ctx, cancelFunc := context.WithCancel(context.Background())
 go func() {
  time.Sleep(5 * time.Second)
  cancelFunc()
 }()

 dosomething(ctx)
}

func test() {

 var testChan = make(chan struct{})

 if testChan == nil {
  fmt.Println("make(chan struct{})后为nil")
 } else {
  fmt.Println("make(chan struct{})后不为nil!!!")
 }

}

输出:

make(chan struct{})后不为nil!!!
I am working!
res2: {}
I am working!
I am working!
I am working!
I am working!
res: {}

而如果 不向没有缓存的cuiChan写入数据,直接close,即

package main

import (
 "context"
 "fmt"
 "time"
)

func dosomething(ctx context.Context) {

 var cuiChan = make(chan struct{})

 //go func() {
 // cuiChan <- struct{}{}
 //}()

 close(cuiChan)

 for {
  select {
  case res := <-ctx.Done():
   fmt.Println("res:", res)
   return
  case res2 := <-cuiChan:
   fmt.Println("res2:", res2)
  default:
   fmt.Println("I am working!")
   time.Sleep(time.Second)
  }
 }
}

func main() {

 test()
 ctx, cancelFunc := context.WithCancel(context.Background())
 go func() {
  time.Sleep(5 * time.Second)
  cancelFunc()
 }()

 dosomething(ctx)
}

func test() {

 var testChan = make(chan struct{})

 if testChan == nil {
  fmt.Println("make(chan struct{})后为nil")
 } else {
  fmt.Println("make(chan struct{})后不为nil!!!")
 }

}

则会一直命中case 2

res2: {}
res2: {}
res2: {}
res2: {}
res2: {}
res2: {}
res2: {}
...
//一直打印下去

更多参考:

深入理解Golang之Context(可用于实现超时机制)[5]

回答我,停止 Goroutine 有几种方法?

golang context的done和cancel的理解 for循环channel实现context.Done()阻塞输出[6]




更多关于channel阻塞与close的代码


package main

import (
 "fmt"
 "time"
)

func main() {
 ch := make(chan string0)
 go func() {
  for {
   fmt.Println("----开始----")
   v, ok := <-ch
   fmt.Println("v,ok", v, ok)
   if !ok {
    fmt.Println("结束")
    return
   }
   //fmt.Println(v)
  }
 }()

 fmt.Println("<-ch一直没有东西写进去,会一直阻塞着,直到3秒钟后")
 fmt.Println()
 fmt.Println()
 time.Sleep(3 * time.Second)

 ch <- "向ch这个channel写入第一条数据..."
 ch <- "向ch这个channel写入第二条数据!!!"

 close(ch) // 当channel被close后, v,ok 中的ok就会变为false

 time.Sleep(10 * time.Second)
}

输出为:

----开始----
<-ch一直没有东西写进去,会一直阻塞着,直到3秒钟后


v,ok 向ch这个channel写入第一条数据... true
----开始----
v,ok 向ch这个channel写入第二条数据!!! true
----开始----
v,ok  false
结束


package main

import (
 "fmt"
 "sync/atomic"
 "time"
)

func main() {
 ch := make(chan string0)
 done := make(chan struct{})

 go func() {
  var i int32

  for {
   atomic.AddInt32(&i, 1)
   select {
   case ch <- fmt.Sprintf("%s%d%s""第", i, "次向通道中写入数据"):

   case <-done:
    close(ch)
    return
   }

   // select随机选择满足条件的case,并不按顺序,所以打印出的结果,在30几次波动
   time.Sleep(100 * time.Millisecond)
  }
 }()

 go func() {
  time.Sleep(3 * time.Second)
  done <- struct{}{}
 }()

 for i := range ch {
  fmt.Println("接收到的值: ", i)
 }

 fmt.Println("结束")
}

输出为:

接收到的值:  第1次向通道中写入数据
接收到的值:  第2次向通道中写入数据
接收到的值:  第3次向通道中写入数据
接收到的值:  第4次向通道中写入数据
接收到的值:  第5次向通道中写入数据
接收到的值:  第6次向通道中写入数据
接收到的值:  第7次向通道中写入数据
接收到的值:  第8次向通道中写入数据
接收到的值:  第9次向通道中写入数据
接收到的值:  第10次向通道中写入数据
接收到的值:  第11次向通道中写入数据
接收到的值:  第12次向通道中写入数据
接收到的值:  第13次向通道中写入数据
接收到的值:  第14次向通道中写入数据
接收到的值:  第15次向通道中写入数据
接收到的值:  第16次向通道中写入数据
接收到的值:  第17次向通道中写入数据
接收到的值:  第18次向通道中写入数据
接收到的值:  第19次向通道中写入数据
接收到的值:  第20次向通道中写入数据
接收到的值:  第21次向通道中写入数据
接收到的值:  第22次向通道中写入数据
接收到的值:  第23次向通道中写入数据
接收到的值:  第24次向通道中写入数据
接收到的值:  第25次向通道中写入数据
接收到的值:  第26次向通道中写入数据
接收到的值:  第27次向通道中写入数据
接收到的值:  第28次向通道中写入数据
接收到的值:  第29次向通道中写入数据
接收到的值:  第30次向通道中写入数据
接收到的值:  第31次向通道中写入数据
结束

每次执行,打印出的结果,在30几次波动

参考资料

[1]

Go语言context包-cancelCtx: https://dashen.tech/2019/06/23/Go%E8%AF%AD%E8%A8%80context%E5%8C%85/#cancelCtx

[2]

context.WithCancel()取消机制的理解: https://blog.csdn.net/weixin_42216109/article/details/123694275

[3]

pkg.go.dev/context#WithCancel:: https://pkg.go.dev/context#WithCancel

[4]

深入理解Golang之Context(可用于实现超时机制): https://blog.csdn.net/qq_25821689/article/details/105850717

[5]

深入理解Golang之Context(可用于实现超时机制): https://blog.csdn.net/qq_25821689/article/details/105850717

[6]

golang context的done和cancel的理解 for循环channel实现context.Done()阻塞输出: https://blog.csdn.net/nakeer/article/details/120897267

本文由 mdnice 多平台发布

相关文章:

context.WithCancel()的使用

“ WithCancel可以将一个Context包装为cancelCtx,并提供一个取消函数,调用这个取消函数,可以Cancel对应的Context Go语言context包-cancelCtx[1] 疑问 context.WithCancel()取消机制的理解[2] 父母5s钟后出门&#xff0c;倒计时&#xff0c;父母在时要学习&#xff0c;父母一走…...

vue3中引入百度地图

话不多说直接开干 1.第一种方式 百度地图地址 打开 https://lbsyun.baidu.com/index.php?title%E9%A6%96%E9%A1%B5 然后点进去地图 然后再这个功能里面选择一个地图&#xff0c;然后跳转页面 然后一直下滑 滑到底部 点击这个 跳转到这个页面 然后点击进入demo这个 然后到这个…...

【Linux-Day8- 进程替换和信号】

进程替换和信号 问题引入 我们发现 终端输入的任意命令的父进程都是bash,这是因为Linux系统是用fork()复制出子进程&#xff0c;然后在子进程中调用替换函数进行进程替换&#xff0c;实现相关命令。 &#xff08;1&#xff09; exec 系列替换过程&#xff1a;pcb 使用以前的只…...

日志文件之间关系和介绍及应用

1.常用日志框架代码举例 Log4j: Log4j是Java中广泛使用的日志框架之一。它提供了灵活的配置选项和丰富的功能&#xff0c;支持日志级别、日志输出目标等。Log4j有1.x版本和2.x版本&#xff0c;其中Log4j 2.x是对1.x的升级和扩展。 Logback: Logback是由Log4j创始人设计的Log4…...

mac电脑屏幕录制Berrycast Mac屏幕录制软件

Berrycast是一款为Mac设计的优秀屏幕录制软件&#xff0c;它让屏幕录制变得简单而高效。以下是Berrycast的一些主要特点&#xff1a; 简单的用户界面&#xff1a;Berrycast拥有直观和简洁的用户界面&#xff0c;使得用户可以轻松上手。高质量的视频输出&#xff1a;Berrycast能…...

机器学习笔记之最优化理论与方法(一)最优化问题概述

机器学习笔记之最优化理论与方法——最优化问题概述 引言什么是最优化问题最优化问题的基本形式最优化问题的分类各分类最优化问题的数学表达约束优化VS无约束优化线性规划VS非线性规划连续优化VS离散优化单目标优化VS多目标优化 引言 从本节开始&#xff0c;将对最优化理论与…...

【ES5新特性一】 严格模式语法变化、全局的JSON对象、编码和解码的方法

前言 ECMAScript 和 JavaScript 的关系 一个常见的问题是&#xff0c;ECMAScript 和 JavaScript 到底是什么关系&#xff1f; 要讲清楚这个问题&#xff0c;需要回顾历史。1996 年 11 月&#xff0c;JavaScript 的创造者 Netscape 公司&#xff0c;决定将 JavaScript 提交给标准…...

Java【手撕滑动窗口】LeetCode 3. “无重复字符的最长子串“, 图文详解思路分析 + 代码

文章目录 前言一、长度最小子数组1, 题目2, 思路分析3, 代码 前言 各位读者好, 我是小陈, 这是我的个人主页, 希望我的专栏能够帮助到你: &#x1f4d5; JavaSE基础: 基础语法, 类和对象, 封装继承多态, 接口, 综合小练习图书管理系统等 &#x1f4d7; Java数据结构: 顺序表, 链…...

学习哈哈哈哈

# 零、学习计划 * 数据库相关 * 索引 * [我以为我对数据库索引很了解&#xff0c;直到我遇到了阿里面试官 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/107487215) * [给我一分钟&#xff0c;让你彻底明白MySQL聚簇索引和非聚簇索引 - 知乎 (zhihu.com)](ht…...

05-基础例程5

基础例程5 1、超声波测距 实验介绍 ​ HC-SR04超声波传感器是一款测量距离的传感器。其原理是利用声波在遇到障碍物反射接收结合声波在空气中传播的速度计算的得出。 外观 管脚功能的定义 VCC&#xff1a;供电电源&#xff1b;Trig&#xff1a;触发信号&#xff1b;Echo&a…...

双基证券:预计未来还会有更多政策来吸引增量资金

双基证券表示&#xff0c;8月27日&#xff0c;活泼资本商场五大方针出台&#xff1a;证券交易印花税折半征收&#xff1b;阶段性收紧IPO节奏&#xff1b;上市房企再融资不受破发、破净和亏本限制&#xff1b;标准控股股东与实际操控人减持行为&#xff1b;融资保证金最低份额由…...

前端:html实现页面切换、顶部标签栏,类似于浏览器的顶部标签栏(完整版)

效果 代码 <!DOCTYPE html> <html><head><style>/* 左侧超链接列表 */.link {display: block;padding: 8px;background-color: #f2f2f2;cursor: pointer;}/* 顶部标签栏 */#tabsContainer {width:98%;display: flex;align-items: center;overflow-x: …...

强化自主可控,润开鸿发布基于RISC-V架构的开源鸿蒙终端新品

2023 RISC-V中国峰会于8月23日至25日在北京召开,峰会以“RISC-V生态共建”为主题,结合当下全球新形势,把握全球新时机,呈现RISC-V全球新观点、新趋势。本次大会邀请了RISC-V国际基金会、业界专家、企业代表及社区伙伴等共同探讨RISC-V发展趋势与机遇,吸引超过百余家业界企业、高…...

软件设计师知识点·1

控制器: (1)指令寄存器(IR) : CPU执行一条指令时,从内存储器取到缓冲寄存器中,再送入IR暂存; (2)程序计数器(PC): 将要执行的下一条指令的地址; (3)地址寄存器(IR): 当前CPU所访问的内存单元地址; (4)指令译码器(ID): 对指令中的操作码字段进行分析解释; 多核CPU可以满足用户…...

修改Jupyter Notebook默认打开路径

这里我是重新下载的anaconda&#xff0c;打开Jupyter之后是默认在C盘的一个路径的&#xff0c;现在我们就来修改一下它的一个默认打开路径&#xff0c;这样在我们后续学习过程中&#xff0c;可以将ipynb后缀的文件放在这个目录下就能查看了。 1、先打开Anaconda Prompt&#x…...

经典卷积网络

目录 一、经典神经网络出现的时间线​编辑 二、LeNet 三、AlexNet 四、VGGNet 五、InceptionNet 六、ResNet 总结&#xff1a; 一、经典神经网络出现的时间线 二、LeNet 背景&#xff1a;LeNet由Yann LeCun于1998年提出&#xff0c;卷积网络开篇之作。 解释&#xff1…...

react+koa+vite前后端模拟jwt鉴权过程

路由组件&#xff08;生成token&#xff09; const Router require(koa/router) const jwt require(jsonwebtoken); const router new Router()const mockDbUserInfo [{nickname: xxxliu,username: Tom,password: 123456,icon: url1},{nickname: xxx,username: John,passw…...

VK1616是LED显示控制驱动电路/LED驱动IC、数显驱动芯片、数码管驱动芯片

产品品牌&#xff1a;永嘉微电/VINKA 产品型号&#xff1a;VK1616 封装形式&#xff1a;SOP16 产品年份&#xff1a;新年份 概述&#xff1a;VK1616是一种数码管或点阵LED驱动控制专用芯片&#xff0c;内部集成有3线串行接口、数据锁存器、LED 驱动等电路。SEG脚接LED阳极&a…...

开箱报告,Simulink Toolbox库模块使用指南(五)——S-Fuction模块(C MEX S-Function)

文章目录 前言 C MEX S-Function 算法原理 原始信号创建 编写S函数 仿真验证 Tips 分析和应用 总结 前言 见《开箱报告&#xff0c;Simulink Toolbox库模块使用指南&#xff08;一&#xff09;——powergui模块》 见《开箱报告&#xff0c;Simulink Toolbox库模块使用…...

摄像头的调用和视频识别

CV_tutorial3 摄像头调用实时播放保存视频 运动目标识别帧差法背景减除法 摄像头调用 创建视频捕捉对象&#xff1a;cv2.VideoCapture() 参数为视频设备的索引号&#xff0c;就一个摄像投的话写0默认&#xff1b; 或者是指定要读取视频的路径。 实时播放 import cv2 import …...

多通道分离与合并

目录 1.多通道分离split() 2.多通道合并merge() 3.Android JNI demo 1.多通道分离split() void cv::split ( InputArray m, OutputArrayOfArrays mv &#xff09; m:待分离的多通道图像。 mv:分离后的单通道图像&#xff0c;为向量vector形式。 2.多通道合并merge…...

JOJO的奇妙冒险

JOJO,我不想再做人了。 推荐一部动漫 JOJO的奇妙冒险 荒木飞吕彦创作的漫画 《JOJO的奇妙冒险》是由日本漫画家荒木飞吕彦所著漫画。漫画于1987年至2004年在集英社的少年漫画杂志少年JUMP上连载&#xff08;1987年12号刊-2004年47号刊&#xff09;&#xff0c;2005年后在集英…...

LeetCode56.合并区间

这道题我想了一会儿&#xff0c;实在想不到比较好的算法&#xff0c;只能硬着头皮写了&#xff0c;然后不断的debug&#xff0c;经过我不懈的努力&#xff0c;最后还是AC&#xff0c;不过效率确实低。 我就是按照最直接的方法来&#xff0c;先把intervals数组按照第一个数star…...

【内推码:NTAMW6c】 MAXIEYE智驾科技2024校招启动啦

MAXIEYE智驾科技2024校招启动啦【内推码&#xff1a;NTAMW6c】 【招聘岗位超多&#xff01;&#xff01;公司食堂好吃&#xff01;&#xff01;】 算法类&#xff1a;感知算法工程师、SLAM算法工程师、规划控制算法工程师、目标及控制算法工程师、后处理算法工程师 软件类&a…...

Python框架【模板继承 、继承模板实战、类视图 、类视图的好处 、类视图使用场景、基于调度方法的类视图】(四)

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱敲代码的小王&#xff0c;CSDN博客博主,Python小白 &#x1f4d5;系列专栏&#xff1a;python入门到实战、Python爬虫开发、Python办公自动化、Python数据分析、Python前后端开发 &#x1f4e7;如果文章知识点有错误…...

对于前端模块化的理解与总结(很全乎)

目录 模块化的好处 模块化的commonJS导入导出 暴露(导出)模块&#xff1a;module.exports value或exports.xxx value 导入模块——使用 es6模块化 方法一逐个导出 方法二默认导出 方法三 方法四 方法五 export 和import 同时存在 多个文件导出到一个文件后在相关文件…...

NewStarCTF 2022 web方向题解 wp

----------WEEK1---------- BUU NewStarCTF 公开赛赛道 WEEK1 [NotPHP] 先看题目&#xff0c;要传参加绕过。 分析一下代码&#xff1a;首先get一个datadata://test/plain,Wel…。然后key1和2用数组可以绕过。num2077a可以绕过弱类型。eval()中的php语句被#注释了&#xff0c…...

WebGL矩阵变换库

目录 矩阵变换库&#xff1a; Matrix4对象所支持的方法和属性如表所示&#xff1a; 方法属性规范&#xff1a; 虽然平移、旋转、缩放等变换操作都可以用一个44的矩阵表示&#xff0c;但是在写WebGL程序的时候&#xff0c;手动计算每个矩阵很耗费时间。为了简化编程&#xf…...

block层:8. deadline调度器

deadline 源码基于5.10 0. 私有数据 struct deadline_data {/** run time data*//** requests (deadline_rq s) are present on both sort_list and fifo_list*/struct rb_root sort_list[2];struct list_head fifo_list[2];/** next in sort order. read, write or both ar…...

DTO,VO,PO的意义与他们之间的转换

DTO&#xff08;Data Transfer Object&#xff09;&#xff1a;数据传输对象&#xff0c;这个概念来源于J2EE的设计模式&#xff0c;原来的目的是为了EJB的分布式应用提供粗粒度的数据实体&#xff0c;以减少分布式调用的次数&#xff0c;从而提高分布式调用的性能和降低网络负…...