实体池实现
引题
这里主要讲解不仅仅是实体池的实现,还有借鉴的编码规则,注视的格式,培养自己在以后的编码过程中养成好的习惯。
接口
池的设计,主要的就是取出和放回操作。每一个模块都需要有一定的自我
监控能力,可以反应自身的使用情况,因此真对实体池,我们提出实体总量和使用情况。接口具体如下:
// 实体池的接口类型。
type Pool interface {
Take() (Entity, error) // 取出实体
Return(entity Entity) error // 归还实体。
Total() uint32 // 实体池的容量。
Used() uint32 // 实体池中已被使用的实体的数量。
}
实现
在实现的过程中,需要计数实体总量,标志实体池的类型,因为要实现扩展性,我们需要外部提供一个函数作为实现实体的标准。还有容纳器包含实体,可以链表活着其他的,但是chan类型似乎更加的友好做为无状态链表操作。还有一个map用于标识实体是否正在用。当然作为并发组件,线程安全是必要的。
// 实体池的实现类型。
type myPool struct {
total uint32 // 池的总容量。
etype reflect.Type // 池中实体的类型。
genEntity func() Entity // 池中实体的生成函数。
container chan Entity // 实体容器。
idContainer map[uint32]bool // 实体ID的容器。
mutex sync.Mutex // 针对实体ID容器操作的互斥锁。
我们需要为每一个结构体提供NewXXX的函数,用于创建。注意这里创建的时候需要同时提供初始化整个实体池,这个池比较简单,是固定大小的,没有动态创建实体。灵活性不高,其实golang的包中提供了这种类似的池操作,大家可以看看源码如何实现。
// 创建实体池。
func NewPool(
total uint32,
entityType reflect.Type,
genEntity func() Entity) (Pool, error) {
if total == 0 {
errMsg :=
fmt.Sprintf("The pool can not be initialized! (total=%d)\n", total)
return nil, errors.New(errMsg)
}
size := int(total)
container := make(chan Entity, size)
idContainer := make(map[uint32]bool)
for i := 0; i < size; i++ {
newEntity := genEntity()
if entityType != reflect.TypeOf(newEntity) {
errMsg :=
fmt.Sprintf("The type of result of function genEntity() is NOT %s!\n", entityType)
return nil, errors.New(errMsg)
}
container <- newEntity
idContainer[newEntity.Id()] = true
}
pool := &myPool{
total: total,
etype: entityType,
genEntity: genEntity,
container: container,
idContainer: idContainer,
}
return pool, nil
}
下面直接给出整个接口函数的实现,不做一一讲解
func (pool *myPool) Take() (Entity, error) { entity, ok := <-pool.container if !ok { return nil, errors.New("The inner container is invalid!") } pool.mutex.Lock() defer pool.mutex.Unlock() pool.idContainer[entity.Id()] = false return entity, nil } func (pool *myPool) Return(entity Entity) error { if entity == nil { return errors.New("The returning entity is invalid!") } if pool.etype != reflect.TypeOf(entity) { errMsg := fmt.Sprintf("The type of returning entity is NOT %s!\n", pool.etype) return errors.New(errMsg) } entityId := entity.Id() casResult := pool.compareAndSetForIdContainer(entityId, false, true) if casResult == 1 { pool.container <- entity return nil } else if casResult == 0 { errMsg := fmt.Sprintf("The entity (id=%d) is already in the pool!\n", entityId) return errors.New(errMsg) } else { errMsg := fmt.Sprintf("The entity (id=%d) is illegal!\n", entityId) return errors.New(errMsg) } } // 比较并设置实体ID容器中与给定实体ID对应的键值对的元素值。 // 结果值: // -1:表示键值对不存在。 // 0:表示操作失败。 // 1:表示操作成功。 func (pool *myPool) compareAndSetForIdContainer( entityId uint32, oldValue bool, newValue bool) int8 { pool.mutex.Lock() defer pool.mutex.Unlock() v, ok := pool.idContainer[entityId] if !ok { return -1 } if v != oldValue { return 0 } pool.idContainer[entityId] = newValue return 1 } func (pool *myPool) Total() uint32 { return pool.total } func (pool *myPool) Used() uint32 { return pool.total - uint32(len(pool.container)) }
感悟
主要感悟在于编码的格式,在哪里写注释,在哪里定义,变量的命名格式都有了新的了解和学习。