标准库的context

从设计角度上来讲, golang的context包提供了一种父routine对子routine的管理功能. 我的这种理解虽然和网上各种文章中讲的不太一样, 但我认为基本上还是很贴合实际的.

context包中定义了一个很重要的接口, 叫context.Context.它的使用逻辑是这样的:

  1. 当父routine需要创建一个子routine的时候, 父routine应当先创建一个context.Context的实例, 这个实例中包括的内容有:

    1. 对子routine生命周期的限制: 比如子routine应该什么时候自杀, 什么条件下自杀. 在服务端编程中, 一个生动的粟子就是: 接收请求的routine在将请求派发给工作routine的时候, 需要告诉工作routine: 超过400ms没处理完你就给我就地爆炸.
    2. 将一些数据共享给子routine.
    3. 在子routine运行过程中, 通过这个Context实例, 可以干涉子routine的生命周期
  2. 子routine拿到父routine创建的context.Context实例后, 开始干活, 干活的过程中, 需要:
    1. 遵守Context实例中关于自身生命周期的约束: 400ms请求没有处理完, 我要就地爆炸
    2. 在自杀之前将自己自杀的消息传递给Context, 这样父routine就可以得知自己的生命状态. 比如我200ms处理完了请求, 我要告诉父routine, 我已经好了
    3. 工作的时候, 如有必要, 从Context中获取一些必要数据.
    4. 工作结束时, 如有必要, 将一些工作成果发送给Context, 以让父routine得知: 比如, 我处理这个请求花费的时间是197ms
    5. 在运行过程中, 从Context接收来自你routine的调度信号

所以说很显然:

  1. Context实例是由父routine创建的. 创建之后传递给子routine作为行为规范
  2. 子routine一般是不允许操作这个Context实例的. 子routine应当耐心倾听, 仅在必要的时候, 比如自杀之前, 将一些信息传递给Context
  3. 一个Context的一生, 从生到死, 是和子routine绑定在一起的. 子routine生, Context生, 子routine死, Context
  4. 良好设计的服务端程序, 每个routine都应该有自己的Context. 而既然routine之间有父子关系树, 那么显然所有routine的Context之间也有一坨树型关系.

我们现在来看context/context.go中是如何实现这套工具的

1 首先是对基本Context的定义

// 定义了一个接口, 名为Context
type Context interface {
// 返回这个Context的死亡时刻, 如果ok == false, 则这个Context是永生的
Deadline() (deadline time.Time, ok bool) // 返回一个channel, 这个channel在Context被Cancel的时候被关闭
// 如果Context是永生的, 则返回一个nil
Done() <-chan struct{} // 在Context活着的时候, (Done()返回的channel还没被关闭), 它返回nil
// 在Context死后, (Done()返回的channel被关闭), 它返回一个error实例用以说明:
// 这个Context是为什么死掉的, 是被Cancel, 还是自然死亡?
Err() error // 返回存储在Context中的通信数据
// 注意: 不要滥用这个接口, 它不是用来给子routine传递参数用的!
Value(key interface{}) interface
} // 定义了两个error实例, 并为其中一个实例的error类型定义了三个方法
var Canceled = errors.New("context canceled") // 用以在Context被Cancel时, 从Err()返回
var DeadlineExceeded error = deadlineExceedError{} // 用以在Context自然死亡时, 从Err()返回
type deadlineExceedError struct{}
func (deadlineExceededError) Error() string { return "context deadline exceeded" } // 实现error接口
func (deadlineExceededError) Timeout() bool { return true }
func (deadlineExceededError) Temporary() bool { return true } // 实现了一个Context类型: emptyCtx, 它有以下特点:
// 0. 这个类型不对外公开, 仅通过后面的两个接口公开它的两个实例
// 1. 不能被Cancel
// 2. 也从不自然死亡, 它是永生的
// 3. 不同的实例之间需要有不同的地址, 所以它没有被定义成struct{}, 而是用一个int来替代
// 4. 它内部也不存储任何数据
type emptyCtx int func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
return
} func (*emptyCtx) Done() <-chan struct{} {
return nil
} func (*emptyCtx) Err() error {
return nil
} func (*emptyCtx) Value(key interface{}) interface{} {
return nil
} // 定义了两个emptyCtx的实例, 并写了两个接口对外公开这两个实例
var (
background = new(emptyCtx)
todo = new(emptyCtx)
) func (e *emptyCtx) String() string {
switch e {
case background:
return "context.Background"
case todo:
return "context.TODO"
}
return "unknown empty Context"
} func Background() Context {
return background
} func TODO() Context {
return todo
}

上面定义了Context的接口规范, 也定义了一个Context接口的实现: emptyCtx, 从代码上可以看出来, 标准库并不公开这个emptyCtx的实现, 你只能从它的公开接口context.Background()context.TODO()来访问两个已经实例化的emptyCtx实例.

这两个实例是用于为顶层routine使用的.下面我们再来看, 可被创建者Cancel的Context是怎么实现的

2 Context接口的实现: 支持Cancel操作的Context: 非公开类cancelCtx

首先是类定义

type cancelCtx struct {
Context // 他爹 mu sync.Mutex // 一个互斥锁, 用来保护其它字段
done chan struct{} // Done()方法的返回值
children map[canceler]struct{} // 这里记录了它的孩子
err error // Err()方法的返回值
}

我们在上面说了, 由于程序中的routine之间是有父子关系树存在的, 那么一个context正常情况下就有可能有孩子, 那么, 如果当前的routine持有的Context实例是可被Cancel的, 那么显然, 它的所有孩子routine, 也应当是可被Cancel的.

这就是为什么cancelCtx类中有Context字段和children字段的原因, 也是为什么children字段是一个map[canceler]struct{}类型的原因: key中记录着所有的孩子, value是没有意义的, 为什么这样写呢? 因为这里把map当成C++中的std::set在用!

key的类型canceler是一个接口, 一个表示Context必须可被Cancel的接口:

type canceler interface {
cancel(removeFromParent bool, err error) // Context接口中的Done方法
Done() <-chan struct{}
}

显然, cancelCtx类本身也是可被Cancel的, 所以它也要实现canceler这个接口

下面是cancelCtx类的方法实现:

// Context.Done的实现: 返回字段 done
func (c *cancelCtx) Done() <-chan struct{} {
c.mu.Lock() // 锁保护done字段的初始化
if c.done == nil {
c.done = make(chan struct{})
}
d := c.done
c.mu.Unlock()
return d
}
// Context.Err的实现
func (c *cancelCtx) Err() error {
c.mu.Lock()
defer c.mu.Unlock()
return c.err
} // String()方法实现
func (c *cancelCtx) String() string {
return fmt.Sprintf("%v.WithCancel", c.Context)
} // canceler.cancel接口实现
// 参数 removeFromParent 指示是否需要把它从它爹的孩子中除名
// 参数 err 将赋值给字段 err, 以供Context.Err方法返回
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 { // 如果err字段有值, 则说明已经被Cancel了
c.mu.Unlock()
return // already canceled
}
c.err = err
if c.done == nil { // 设置c.done, 以供Done方法返回
c.done = closedchan
} else {
close(c.done)
} // 挨个cancel它的所有孩子, 子随父死的时候, 并不除名父子关系
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 {
removeChild(c.Context, c)
}
} // 这是一个全局复用的, 被关闭的channel, 用于被Context.Done返回使用
var closedchan = make(chan struct{}) func init() {
close(closedchan)
}

可以看到, cancelCtx本身并没有实现所有的Context接口中的方法. 其余没有实现的接口是通过Context这个没有指定字段名的字段实现的. 这是go的特殊语法糖: 继承接口.

在一个类型定义中, 声明一个接口类型字段, 并且还不指定字段的名称, 这代表

  1. 当前类型必然实现了接口类型
  2. 当调用接口方法时, 默认调用的是子字段的方法, 除非当前类型显式overwrite了一些方法的实现

其实就是一种更为灵活的继承写法

我们再来看, 当父routine需要创建一个带有Cancel功能的Context实例的时候, 应该怎么办:

// 首先是定义一个函数指针别名
type CancelFunc func() // 再就是父routine创建带Cancel功能的子Context的函数
// 父routine将自己的Context实例传入, 这个函数会返回子Context(带Cancel功能)
// 还会返回一个可调用对象 cancel, 调用这个对象(函数), 就能达到Cancel的功能
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := newCancelCtx(parent) // 创建一个cancelCtx的实例
propagateCancel(parent, &c)
return &c, func() { c.cancel(true, Canceled) }
} // 下面是WithCancel中引用的两个私有函数的实现 // 创建一个cancelCtx实例
func newCancelCtx(parent Context) cancelCtx {
return cancelCtx{Context: parent} // 把爹先记录下来
} func propagateCancel(parent Context, child canceler) {
// 如果父Context是不可Cancel, 什么也不做
if parent.Done() == nil {
return // parent is never canceled
}
// 如果父Context本身是可Cancel的
if p, ok := parentCancelCtx(parent); ok {
// 进入此分支, 说明父Context是以下三种之一:
// 1. 是一个cancelCtx, 本身就可被Cancel
// 2. 是一个timerCtx, timerCtx是canctx的一个子类, 也可被Cancel
// 3. 是一个valueCtx, valueCtx继承体系上的某个爹, 是以上两者之一
// 那么p就是那个父Context的继承体系中的cancelCtx实例
p.mu.Lock()
if p.err != nil {
// 若p已经被Cancel或自然死亡, 作为儿子, 就必须死了
// 直接调用p.cancel
child.cancel(false, p.err)
} else {
// 若p还活着, 就把儿子添加到它的儿子列表中去
if p.children == nil {
p.children = make(map[canceler]struct{})
}
p.children[child] = struct{}{}
}
p.mu.Unlock()
} else {
// 进入此分支, 说明父Context虽然可被Cancel
// 但并不是标准库中预设的cancelCtx或timerCtx两种可被Cancel的类型
// 这意味着这个特殊的父Context, 内部并不能保证记录了所有儿子的列表
// 这里就得新开一个routine, 时刻监视着父Context的生存状态
// 一旦父Context死亡, 就立即调用child.cancel把儿子弄死
go func() {
select {
case <-parent.Done(): // 如果爹死了, 把孩子弄死
child.cancel(false, parent.Err())
case <-child.Done(): // 如果孩子死了, 什么也不做
}
}()
}
} // 判断Context实例是否是一个可被Cancel的类型
// 标准库中可被Cancel的Context类型共有三种:
// 1. cancelCtx
// 2. timerCtx
// 仅有这两种
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
for {
switch c := parent.(type) {
case *cancelCtx:
return c, true
case *timerCtx:
return &c.cancelCtx, true
case *valueCtx:
parent = c.Context
default:
return nil, false
}
}
}

3 当你使用WithCancel

一个简单的例子

这里来捋一捋, 当你调用WithCancel创建一个可被Cancel的Context实例时, 都发生了些什么:

// 第一步, 创建者routine本身必须持有一个Context
// 这里假定创建者就是main routine
// 我们调用 Background创建一个不可被Cancel, 不会自杀的Context
contextOfMain := ctx.Background() // 第二步: 调用WithCancel创建子Context
contextOfSubRoutine, cancelFuncOfSubRoutine := ctx.WithCancel(contextOfMain)

用起来是十分简单的, 我们再来捋一捋第二步背后都发生了什么, 下面是伪码:

WithCancel(contextOfMain) {
// 第一步: 调用newCancelCtx创建了一个 cancelCtx 私有类的实例, 长这样:
c := cancelCtx {
Context: contextOfMain,
mu : 默认值,
done : nil, // 虽然现在是nil, 但在调用Done()方法时会返回一个make(chan struct{})
children: nil,
err : nil,
}
// 第二步, 调用propagateCancel(contextOfMain, c)
// 内部大概发生这样:
{
if contextOfMain.Done() == nil {
// 什么也没有发生
}
}
// 第三步: 返回c, 并且构造一个CancelFunc返回
// 先是返回c
return &c // 这里返回的是c的地址
// 再是原地构造一个CancelFunc
func () {
c.cancel(true, Canceled)
} /*
注意:
c.cancel调用的是cancelCtx.cancel方法
Canceled是一个全局变量, 值 == errors.New("context canceled")
*/
}

然后, 你将这个创建好的cancelFuncOfSubRoutine传递给新启动的子routine, 过了几分钟, 你调用cancelFuncOfSubRoutine()意图主动Cancel掉子routine的时候, 内部是这样执行的:

// 其实内部执行的是
contextofSubRoutine.cancel(true, errors.New("context canceled")) { contextofSubRoutine.mu.Lock()
contextofSubRoutine.err = errors.New("context canceled")
contextofSubRoutine.done = closedchan // 这是一个已经被关闭的chan struct{}
for child := range contextofSubRoutine.children {
// 递归Cancel掉子routine下的所有孙子
// 而实际上它并没有孩子, 所以什么也不做
child.cancel(false, errors.New("context canceled"))
}
contextofSubRoutine.children = nil // 一把火把孙子的尸首全烧了
contextofSubRoutine.mu.Unlock() removeChild(contextofSubRoutine.Context, contextofSubRoutine) {
p, ok := parentCancelCtx(contextOfMainRoutine, contextofSubRoutine)
// 由于contextOfMainRoutine的类型是emptyCtx
// 所以parentCancelCtx函数返回的是 nil, false
所以, 什么也不做, 就返回了
} }

一个稍微复杂一点的例子

我们假设当前进程中的routine树(即是Context树)关系如下所示:

mainContext // emptyCtx
|
\-> subContext // cancelCtx
|
\-> subsubContext1 // cancelCtx
\-> subsubContext2 // cancenCtx
\-> subsubContext3 // cancelCtx

现在, subContext要创建第四个subsubContext4, 它会这样做:

// 在subRoutine中
subsubContext4, cancelFunc4 := ctx.WithCancel(subContext) {
// 内部是这样的: // step 1: 调用 ctx.newCancelCtx()
subsubContext4 := &cancelCtx {
Context: subContext,
mu : 默认值,
done : nil, // 虽然现在是nil, 但在调用Done()时会返回一个make(chan struct{})
children: nil,
err : nil,
}
// step 2: 调用propagateCancel(subContext, subsubContext4)
{
// p, ok := parentCancelCtx(subContext)
{
p := subContext
ok := true
}
// 这里将subsubContext4加到subContext的儿子列表中去
subContext.mu.Lock()
subContext[subsubContext4] = struct{}{}
subContext.mu.Unlock()
}
// step 3: 创建CancelFunc
cancelFunc4 := func() {
subsubContext4.cancel(true, errors.New("context canceled"))
}
}

创建结束后, subContext长这样:

subContext := &cancelCtx {
Context: mainContext,
mu : 默认值,
done : nil, // 虽然现在是nil, 但在调用Done()时会返回一个make(chan struct{})
children : {
subsubContext1 : struct{}{},
subsubContext2 : struct{}{},
subsubContext3 : struct{}{},
subsubContext4 : struct{}{},
},
err : nil
}

然后, 当subRoutine调用cancelFunc4意图弄死subsubRoutine4的时候, 会发生如下:

subsubContext4.cancel(true, errors.New("context canceled")){
subsubContext4.mu.Lock()
subsubContext4.err = errors.New("context canceled")
subsubContext4.done = closedchan
for child := range subsubContext4.children {
// subsubContext4并没有孩子
// 什么也不做
}
subsubContext4.children = nil
subsubContext4.mu.Unlock() removeChild(subsubContext, subsubContext4) {
// 从subContext的children中删除 subsubContext4
}
}

而如果, 在Cancel了subRoutine4后, 主线程中直接要Cancel SubRoutine的话, 会发生什么? 会发生如下:

subContext.cancel(true, errors.New("context canceled")) {
subContext.mu.Lock()
subContext.err = errors.New("context canceled")
subContext.done = closedchan
for child := range subContext.children {
// 这里会调用 subsubContext1/2/3的cancel方法
child.cancel(false, errors.New("context canceled"))
}
subContext.children = nil
subContext.mu.Unlock()
// 最后一步本身要将subContext从他爹那里除名
// 但由于他爹是个emptyCtx, 所以什么也不做
}

基本把整个cancelCtx的流程理解掉之后, 后面的所谓的带DeadLine的Context就非常好理解了

4 Context接口的实现: 支持Deadline()操作的Context: 非公开类timerCtx

timerCtx实现了定时器功能: 到达指定时刻, 自杀.

首先是类定义:

type timerCtx struct {
cancelCtx
timer *time.Timer // Under cancelCtx.mu. deadline time.Time
}

需要注意的是两点:

  1. 它继承了cancelCtx
  2. 定时功能是由标准库的time.Timer实现的

先看它的Deadline()方法的实现, 这个方法就是它的灵魂

func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
return c.deadline, true
}

只是简单的字段deadline的getter

它还重写了canceler.cancel方法:

func (c *timerCtx) cancel(removeFromParent bool, err error) {
c.cancelCtx.cancel(false, err)
if removeFromParent {
// Remove this timerCtx from its parent cancelCtx's children.
removeChild(c.cancelCtx.Context, c)
}
c.mu.Lock()
if c.timer != nil { // 主要是在Cancel时停掉内部的计时器
c.timer.Stop()
c.timer = nil
}
c.mu.Unlock()
}

再来看和WithCancel平级的WithDeadLine函数:

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
// 如果父Context也是一个有死期的Context, 并且死期还在儿子想死之前
// 那么只是简单的调用WithCancel来给创建一个可被Cancel的cancelCtx即可
// 这样, 创建出的子Context调用Deadline()方法时, 实质上调用的是他爹的Deadline(), 语义上完美完成任务
return WithCancel(parent)
} // 不然, 得带个定时器, 先把死期记在deadline字段中
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: d,
}
// 设置逻辑: 爹死的时候儿子也得死, 并且如果可能, 把儿子记在爹的children字段中
propagateCancel(parent, c) // 如果死期已经过了
dur := time.Until(d)
if dur <= 0 {
// 原地自杀, 但还是要返回这个Context
c.cancel(true, DeadlineExceeded) // deadline has already passed
return c, func() { c.cancel(true, Canceled) }
}
c.mu.Lock() // 创建定时器
defer c.mu.Unlock()
if c.err == nil {
c.timer = time.AfterFunc(dur, func() {
c.cancel(true, DeadlineExceeded)
})
}
return c, func() { c.cancel(true, Canceled) }
}

注意:

  1. WithDeadline也返回一个CancelFunc
  2. 如果爹死的比儿子预想的还早, 那只不过是用爹调用WithCancel创建了一个可Cancel的Context
  3. 如果死期在调用WithDeadline的时候已经到达了, 那么依然要给调用方返回一个死掉的儿子尸体, 只不过它的Done()Err()会指出这个Context已经死掉了
  4. 定时器的到期回调, 调用的就是canceler.cancel方法

可以看到, timerCtx只是对cancelCtx在功能上的追加. WithDeadline也只是简单的追加了一个定时器,逻辑还是比较简单的.

所以,如果到这里你已经脑子有点乱掉了, 还是要回头把cancelCtx理清

另外, 这里还提供了一个名为WithTimeout的函数, 其实与WithDeadline是完全等价的, 实现如下:

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}

这里我们就不再着重分析WithDeadline/WithTimeout的逻辑流程了

5 Context接口的实现: 带数据共享的非公开类valueCtx

整个定义十分简单, 就是在Context接口之上, 实现了对数据的存储而已, 并且只能存储一个key, 全文如下:


func WithValue(parent Context, key, val interface{}) Context {
if key == nil {
panic("nil key")
}
if !reflect.TypeOf(key).Comparable() {
panic("key is not comparable")
}
return &valueCtx{parent, key, val}
} type valueCtx struct {
Context
key, val interface{}
} func (c *valueCtx) String() string {
return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val)
} func (c *valueCtx) Value(key interface{}) interface{} {
if c.key == key {
return c.val
}
return c.Context.Value(key)
}

可以看到, valueCtx将所有重要功能的实现都委托到了父类上, 这在使用时就非常信赖父类, 也就是说, 如果你想仅仅依靠标准库的这些公开接口, 来直接在主routine下开启一个, 既带数据共享, 还带可Cancel功能的子Context的话, 你只能这样写:

mainContext := ctx.Background() // 主routine
// 为了创建一个带Cancel功能的valueCtx, 首先需要创建一个cancelCtx
tmpContext, subRoutineCancelFunc := ctx.WithCancel(mainContext)
subContext := ctx.WithValue(tmpContext, key, value)

6 总结

标准库的context包, 只实现了几个基本的Context接口的实现, 并且还很受限的只能通过公开接口WithXXX来创建, 这很显然是在鼓励你做下面的事情:

  1. 在已有的Context接口定义上, 定义你自己的Context实现类.
  2. 不要将过多的逻辑放置在Context中去, 让它只干好自己该干的事情: 那就是父子routine间生命周期的管理

并且显然context包只实现了Context的语义, 并没有实现相关的routine的操作: 比如在Cancel时掐死子进程, 在Deadline到期的时候自动自杀等. 这还需要由使用者自行实现.

golang中的context包的更多相关文章

  1. golang中的reflect包用法

    最近在写一个自动生成api文档的功能,用到了reflect包来给结构体赋值,给空数组新增一个元素,这样只要定义一个input结构体和一个output的结构体,并填写一些相关tag信息,就能使用程序来生 ...

  2. golang中container/list包源码分析

    golang源码包中container/list实际上是一个双向链表 提供链表的一些基本操作,下面就结合定义和接口进行下说明 1. 定义 // Element is an element of a l ...

  3. golang中container/heap包源码分析

    学习golang难免需要分析源码包中一些实现,下面就来说说container/heap包的源码 heap的实现使用到了小根堆,下面先对堆做个简单说明 1. 堆概念 堆是一种经过排序的完全二叉树,其中任 ...

  4. golang中的rpc包用法

    RPC,即 Remote Procedure Call(远程过程调用),说得通俗一点就是:调用远程计算机上的服务,就像调用本地服务一样. 我所在公司的项目是采用基于Restful的微服务架构,随着微服 ...

  5. golang中net/http包的简单使用

    一.介绍 http包提供了http客户端和服务端的实现 Get,Head,Post和PostForm函数发出http.https的请求 程序在使用完回复后必须关闭回复的主体 #简单的访问网站,由于没有 ...

  6. golang中os/exec包用法

    exec包执行外部命令,它将os.StartProcess进行包装使得它更容易映射到stdin和stdout,并且利用pipe连接i/o. 1.func LookPath(file string) ( ...

  7. 关于Golang中database/sql包的学习

    go-sql-driver 请求一个连接的函数有好几种,执行完毕处理连接的方式稍有差别,大致如下: db.Ping() 调用完毕后会马上把连接返回给连接池. db.Exec() 调用完毕后会马上把连接 ...

  8. golang 中的 time 包的 Ticker

    真实的应用场景是:在测试收包的顺序的时候,加了个 tick 就发现丢包了 那么来看一个应用例子: package main import ( "fmt" "runtime ...

  9. Golang中database/sql包

    驱动 github.com/go-sql-driver/mysql 请求一个连接的函数有好几种,执行完毕处理连接的方式稍有差别,大致如下: db.Ping() 调用完毕后会马上把连接返回给连接池. d ...

随机推荐

  1. mysql之用户管理

    本文内容: 用户的介绍 查看用户 创建用户帐户 修改账户 删除帐户 关于匿名用户 首发日期:2018-04-19 用户的介绍: mysql的客户端连接是以用户名来登录服务端. 服务端可以对用户的权限来 ...

  2. JavaWeb入门笔记

    Java web笔记 一.HTTP协议 HTTP(超文本传输协议),它是一种主流B/S架构中应用的通信协议.具有以下特点: 1.无状态 服务端不会记录客户端每次提交的请求,服务器一旦相应客户端之后,就 ...

  3. Sql Server XML

    实验数据: Create table xmldata (name NVARCHAR(20), age int, sex NVARCHAR(5) ) INSERT INTO xmldata VALUES ...

  4. IPerf——网络测试工具介绍与源码解析(3)

    [线程的生成]   生成线程时需要传入一个thread_Settings类型的变量,thread_Settings包含所有线程运行时需要的信息,命令行选项参数解析后所有得到的属性都存储到该类型的变量中 ...

  5. 简单的bfs

    这里主要是写的一个简单的bfs,实例运行了RMAT10无向图,总共有1024个顶点.这种简单的bfs算法存在很明显的缺陷,那就是如果图数据过大,那么进程将会直接被系统杀死. 代码如下: #includ ...

  6. 【算法】LeetCode算法题-Implement strStr

    这是悦乐书的第151次更新,第153篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第10题(顺位题号是28).给定两个任意字符串haystack.needle,返回hay ...

  7. June 7. 2018 Week 23rd Thursday

    Half is worse than none at all. 一知半解比一无所知更痛苦. From Westworld. If we go looking for the truth, get th ...

  8. Oauth2.0[笔记]

    背景 如果资源服务器只是提供资源给自己的应用,使用帐号密码做身份认证倒没什么问题,但如果需要提供资源给第三方应用,就会出现第三方应用需要与资源服务器共享身份凭证,这时会出现几个问题: 1.第三方应用需 ...

  9. android开发——用户头像

    最近,小灵狐得知了一种能够加快修炼速度的绝世秘法,那便是修炼android神功.小灵狐打算用android神功做一个app,今天他的修炼内容就是头像功能.可是小灵狐是个android小白啊,所以修炼过 ...

  10. Linux之文档与目录结构

    Linux文件系统结构 Linux目录结构的组织形式和Windows有很大的不同.首先Linux没有“盘(C盘.D盘.E盘)”的概念.已经建立文件系统的硬盘分区被挂载到某一个目录下,用户通过操作目录来 ...