这个空的select-case-default代码块有什么效果?

huangapple go评论88阅读模式
英文:

What is the effects for this empty select-case-default code block?

问题

我正在尝试理解一个池库的代码,当实例化一个池结构体时,调用一个名为startCleanerLocked(t Duration)的函数,在这个函数中,有一个空的select...case...default...代码块,我无法理解这个代码块的作用。

Pool接口定义如下:

// Pool接口。
type Pool interface {
	Get(ctx context.Context) (io.Closer, error)
	Put(ctx context.Context, c io.Closer, forceClose bool) error
	Close() error
}

List结构体实现了Pool接口,定义如下:

type List struct {
	// New是一个由应用程序提供的用于创建和配置项目的函数。
	//
	// 从new返回的项目不能处于特殊状态
	// (订阅了pubsub频道,启动了事务等)。
	New func(ctx context.Context) (io.Closer, error)

	// mu保护下面定义的字段。
	mu     sync.Mutex
	cond   chan struct{}
	closed bool
	active int
	// 清理过期项目
	cleanerCh chan struct{}

	// 最近使用的项目堆栈,最近使用的项目在前面。
	idles list.List

	// Config池配置
	conf *Config
}

当创建一个新的池时,会调用startCleanerLocked(t Duration)函数:

// NewList创建一个新的池。
func NewList(c *Config) *List {
	// 检查配置
	if c == nil || c.Active < c.Idle {
		panic("config nil or Idle Must <= Active")
	}
	// 创建池
	p := &List{conf: c}
	p.cond = make(chan struct{})
	p.startCleanerLocked(time.Duration(c.IdleTimeout))
	return p
}

startCleanerLocked(t Duration)函数中,有一个select...case...default代码块:

// startCleanerLocked
func (p *List) startCleanerLocked(d time.Duration) {
	if d <= 0 {
		// 如果设置为0,staleCleaner()将直接返回
		return
	}
	if d < time.Duration(p.conf.IdleTimeout) && p.cleanerCh != nil {
		select {
		case p.cleanerCh <- struct{}{}:
		default:
		}
	}
	// 只运行一次,清理过期项目。
	if p.cleanerCh == nil {
		p.cleanerCh = make(chan struct{}, 1)
		go p.staleCleaner()
	}
}

这个代码块的作用是什么:

select {
	case p.cleanerCh <- struct{}{}:
	default:
}

看起来它似乎没有什么作用...

staleCleaner()函数中,也有一个相同的空的select...case...case,同样无法理解其作用:

// staleCleaner清理过期项目的处理。
func (p *List) staleCleaner() {
	ticker := time.NewTicker(100 * time.Millisecond)
	for {
		select {
		case <-ticker.C:
		case <-p.cleanerCh: // maxLifetime被更改或数据库被关闭。
		}
		p.mu.Lock()
		if p.closed || p.conf.IdleTimeout <= 0 {
			p.mu.Unlock()
			return
		}
		for i, n := 0, p.idles.Len(); i < n; i++ {
			e := p.idles.Back()
			if e == nil {
				// 不可能发生
				break
			}
			ic := e.Value.(item)
			if !ic.expired(time.Duration(p.conf.IdleTimeout)) {
				// 不需要继续。
				break
			}
			p.idles.Remove(e)
			p.release()
			p.mu.Unlock()
			ic.c.Close()
			p.mu.Lock()
		}
		p.mu.Unlock()
	}
}
英文:

I'm trying to understand a pool library codes, and when instancing a pool struct, call a function named startCleanerLocked(t Duration), in this function, there's one empty select...case...default... code block, I cann't understand what is the effect for this code block.

Pool Interface is:

// Pool interface.
type Pool interface {
	Get(ctx context.Context) (io.Closer, error)
	Put(ctx context.Context, c io.Closer, forceClose bool) error
	Close() error
}

List Struct implement Pool Interface,

type List struct {
	// New is an application supplied function for creating and configuring a
	// item.
	//
	// The item returned from new must not be in a special state
	// (subscribed to pubsub channel, transaction started, ...).
	New func(ctx context.Context) (io.Closer, error)

	// mu protects fields defined below.
	mu     sync.Mutex
	cond   chan struct{}
	closed bool
	active int
	// clean stale items
	cleanerCh chan struct{}

	// Stack of item with most recently used at the front.
	idles list.List

	// Config pool configuration
	conf *Config
}

when Create a new pool, startCleanerLocked(t Duration) function be called:

// NewList creates a new pool.
func NewList(c *Config) *List {
	// check Config
	if c == nil || c.Active &lt; c.Idle {
		panic(&quot;config nil or Idle Must &lt;= Active&quot;)
	}
	// new pool
	p := &amp;List{conf: c}
	p.cond = make(chan struct{})
	p.startCleanerLocked(time.Duration(c.IdleTimeout))
	return p
}

and in startCleanerLocked(t Duration), there is a select...case...default:

// startCleanerLocked
func (p *List) startCleanerLocked(d time.Duration) {
	if d &lt;= 0 {
		// if set 0, staleCleaner() will return directly
		return
	}
	if d &lt; time.Duration(p.conf.IdleTimeout) &amp;&amp; p.cleanerCh != nil {
		select {
		case p.cleanerCh &lt;- struct{}{}:
		default:
		}
	}
	// run only one, clean stale items.
	if p.cleanerCh == nil {
		p.cleanerCh = make(chan struct{}, 1)
		go p.staleCleaner()
	}
}

what's the effect for this code block:

select {
	case p.cleanerCh &lt;- struct{}{}:
	default:
}

Seems it's nothing to do...

and in staleCleaner(), there is a same empty select..case...case, also cannot undestand its effect:

// staleCleaner clean stale items proc.
func (p *List) staleCleaner() {
	ticker := time.NewTicker(100 * time.Millisecond)
	for {
		select {
		case &lt;-ticker.C:
		case &lt;-p.cleanerCh: // maxLifetime was changed or db was closed.
		}
		p.mu.Lock()
		if p.closed || p.conf.IdleTimeout &lt;= 0 {
			p.mu.Unlock()
			return
		}
		for i, n := 0, p.idles.Len(); i &lt; n; i++ {
			e := p.idles.Back()
			if e == nil {
				// no possible
				break
			}
			ic := e.Value.(item)
			if !ic.expired(time.Duration(p.conf.IdleTimeout)) {
				// not need continue.
				break
			}
			p.idles.Remove(e)
			p.release()
			p.mu.Unlock()
			ic.c.Close()
			p.mu.Lock()
		}
		p.mu.Unlock()
	}
}

答案1

得分: 0

select {
case p.cleanerCh <- struct{}{}:
default:
}

这是一个非阻塞的 select 语句(因为有一个 default: 分支)。

如果 p.cleanerCh 通道的另一端有一个接收器 goroutine,也就是说有一个 goroutine 正在等待接收操作 <-p.cleanerCh,那么 case p.cleanerCh <- struct{}{} 会立即执行,从而解除接收操作 <-p.cleanerCh 的阻塞,然后 goroutine 可以继续执行后续的语句。

如果没有接收器 goroutine,那么 default: 分支会立即执行,startCleanerLocked 函数可以继续执行 select 语句后面的语句。


select {
case <-ticker.C:
case <-p.cleanerCh: // maxLifetime was changed or db was closed.
}

这是一个阻塞的 select 语句(因为没有 default: 分支)。

这个 select 语句会阻塞 for 循环,直到两个通信分支中的一个准备好接收数据。

英文:
select {
case p.cleanerCh &lt;- struct{}{}:
default:
}

This is a non-blocking select statement. (because there is a default: case)

If there is a receiver goroutine at the other end of the p.cleanerCh channel, i.e. there is a goroutine that's currently "waiting" at a receive operation i.e. &lt;-p.cleanerCh, then the case p.cleanerCh &lt;- struct{}{} is executed immediately which effectively unblocks the receive operation &lt;-p.cleanerCh and then the goroutine can proceed to execute whatever statements follow.

If there is no receiver goroutine then the default: case is immediately executed and the surrounding startCleanerLocked function can proceed to execute whatever statement follow the select statement.


select {
case &lt;-ticker.C:
case &lt;-p.cleanerCh: // maxLifetime was changed or db was closed.
}

This is a blocking select statement. (because there is no default: case)

This select statement blocks the for loop until one of the two communication cases is ready to receive.

huangapple
  • 本文由 发表于 2022年2月14日 17:14:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/71109453.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定