对象池模式的变形

本文大部分内容翻译至《Pro Design Pattern In Swift》By Adam Freeman,一些地方做了些许修改,并将代码升级到了Swift2.0,翻译不当之处望多包涵。

对象池模式的变形

这里我们将介绍如何改变对象池模式来满足不同的需求。


理解对象池模式的变种

实现对象池包含四种策略:

  • 对象的创建策略
  • 对象的重用策略
  • 空对象池策略
  • 对象的分配策略

通过改变这些策略,可以满足你需要的对象池的合适的实现。


理解对象的创建策略

在上一章中,我们使用了饥饿策略来创建对象,就是指对象在使用之前就已经被创建了出来。

Library.swift

...
    private init(stockLevel:Int) {
        books = [Book]()
        
        for count in 1 ... stockLevel {
            books.append(Book(author: "Dickens, Charles", title: "Hard Times",
            stock: count))
      }
        pool = Pool<Book>(items:books)
    }
...

这种策略适用于当面对代表真实世界资源的对象时,因为对象的个数事先就已经非常的清楚。但是这种策略的缺点也显而易见,那就是即使是没有任何需求的时候,所有的对象也都将被创建和配置。

另一种策略就是延迟创建策略,就是只有当有需求时对象才被创建。这里我们创建一个BookSeller类:

BookSerller.swift

import Foundation

class BookSeller {
    class func buyBook(author:String, title:String, stockNumber:Int) -> Book {
        return Book(author: author, title: title, stock: stockNumber)
    }
}

BookSeller类定义了一个类方法用来创建Book对象。下面我们将修改Pool类来支持延迟对象的创建直到有需求发生。

Pool.swift

import Foundation

class Pool<T> {
    private var data = [T]()
    private let queue = dispatch_queue_create("arrayQ", DISPATCH_QUEUE_SERIAL)
    private let semaphore:dispatch_semaphore_t
    private var itemCount = 0
    private let maxItemCount:Int
    private let itemFactory: () -> T
    
    init(maxItemCount:Int, factory:() -> T) {
        self.itemFactory = factory
        self.maxItemCount = maxItemCount
        semaphore = dispatch_semaphore_create(maxItemCount)
    }
    
    func getFromPool() -> T? {
       var result:T?
    
       if (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER) == 0) {
           dispatch_sync(queue, { () -> Void in
        
            if (self.data.count == 0 && self.itemCount < self.maxItemCount) {
                result = self.itemFactory()
                self.itemCount++
            } else {
                result = self.data.removeAtIndex(0);
            }
        })
        
        }
        return result
    }
    
    func returnToPool(item:T) {
        dispatch_async(queue) { () -> Void in
           self.data.append(item)
           dispatch_semaphore_signal(self.semaphore)
        }
        
    }
    
    func processPoolItems(callback:[T] -> Void) {
        dispatch_barrier_sync(queue, {() in
            callback(self.data)
        })
    }
}

我们改变了Pool类的初始化方法,它接收一个用来创建对象的闭包和一个代表对象池大小的Int类型值。

另一个改变是我们增加了一个方法processPoolItems用来记录借出信息。因为在原来代码中,对象的创建是由Library类中来实现,所以Library中可以保持对Book数组对引用。这里我们为了实现使用了回调方法来操作Book数组,而dispatch_barrier_sync保证了队列中所有任务完成后才会执行回调方法。
最后我们修改Library 类实现延迟创建:

Library.swift

import Foundation

class Library {

    private let pool:Pool<Book>
    static let sharedInstance = Library(stockLevel: 200)
    
    private init(stockLevel:Int) {
        var stockId = 1
        pool = Pool<Book>(maxItemCount: stockLevel, factory: { () -> Book in
            return BookSeller.buyBook("Dickens, Charles",
                title: "Hard Times", stockNumber: stockId++)
        })
    }
    
   
    func checkoutBook(reader:String) -> Book? {
        let book = pool.getFromPool()
        book?.reader = reader
        book?.checkoutCount++
        return book
    }
    
    func returnBook(book:Book) {
        book.reader = nil
        pool.returnToPool(book)
    }
    
    func printReport() {
        pool.processPoolItems { (books) -> Void in
            for book in books {
                print("...Book#\(book.stockNumber)...")
                print("Checked out \(book.checkoutCount) times")
                
                if (book.reader != nil) {
                    print("Checked out to \(book.reader!)")
                } else {
                    print("In stock")
                }
            }

        
        }
    }
}

如果执行程序,我们也许会得到以下的结果:

Starting...
All blocks complete
...Book#7...
Checked out 2 times
In stock
...Book#4...
Checked out 4 times
In stock
...Book#13...
Checked out 1 times
In stock
...Book#1...
Checked out 1 times
In stock
...Book#2...
Checked out 1 times
In stock
...Book#3...
Checked out 1 times
In stock
...Book#9...
Checked out 1 times
In stock
...Book#10...
Checked out 1 times
In stock
...Book#8...
Checked out 1 times
In stock
...Book#5...
Checked out 2 times
In stock
...Book#11...
Checked out 1 times
In stock
...Book#6...
Checked out 3 times
In stock
...Book#12...
Checked out 1 times
In stock
There are 13 books in the pool

因为我们增加了一个随机时间等待的缘故,Book的确切数字每次都会不同。最糟糕的情况是20本书全都被创建,那就意味着图书馆对于200本书的购买总额估计过高。如果我们这里使用饥饿策略,那么200本的副本都将被创建,延迟策略的优点就在于此。


理解对象的重用策略

对象池模式的性质意味着池中的对象会重复的分配给请求对象,这样就导致了一个风险那就是对象被归还的时候处在一个糟糕的状态。在现实世界的图书馆中,这就好比归还的书出现了撕裂或者缺页。在软件中,就意味着对象出现了不一致的状态或者遭遇了不可恢复的错误。

最简单的解决方法是信任策略,这就是你相信归还的对象都会是一个可重用的状态。对象池中管理的Book对象目前还没发生这种问题是因为它们几乎没提供任何可变的公开状态。

另一种就是不信任策略,当对象被归还之前做一个检查来确保它们都是可重用的状态。那些不能重用的对象都将被丢弃。

Caution:不信任策略只能用于那些有能力替代对象的对象池。如果没有能力去替代对象,那么对象池最终会耗尽。

我们将改变Book对象的使用方法来实现它们只能被借出特定的次数,反应了一个事实那就是书会承担磨损和消耗直到没办法在使用。下面我们定义一个PoolItem的协议。

PoolItem.swift

import Foundation

@objc protocol PoolItem {
    var canReuse:Bool {get}
}

PoolItem 协议定义了一个只读属性canReuse,当对象被归还时我们将检查这个属性如果是false该对象将被遗弃。

Tip:注意到我们使用了@objc标注,这将支持对象向下转型成PoolItem,让我们能在Pool中读取canReuse属性。

接下来修改Pool类:

Pool.swift

import Foundation

class Pool<T> {
    private var data = [T]()
    private let queue = dispatch_queue_create("arrayQ", DISPATCH_QUEUE_SERIAL)
    private let semaphore:dispatch_semaphore_t
    private var itemCount = 0
    private let maxItemCount:Int
    private let itemFactory: () -> T
    
    init(maxItemCount:Int, factory:() -> T) {
        self.itemFactory = factory
        self.maxItemCount = maxItemCount
        semaphore = dispatch_semaphore_create(maxItemCount)
    }
    
    func getFromPool() -> T? {
       var result:T?
    
       if (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER) == 0) {
           dispatch_sync(queue, { () -> Void in
        
            if (self.data.count == 0 && self.itemCount < self.maxItemCount) {
                result = self.itemFactory()
                self.itemCount++
            } else {
                result = self.data.removeAtIndex(0);
            }
        })
        
        }
        return result
    }
    
    func returnToPool(item:T) {
        dispatch_async(queue) { () -> Void in
           let pitem = item as AnyObject as? PoolItem;
           if (pitem == nil || pitem!.canReuse) {
              self.data.append(item)
              dispatch_semaphore_signal(self.semaphore)
           }
        }
        
    }
    
    func processPoolItems(callback:[T] -> Void) {
        dispatch_barrier_sync(queue, {() in
            callback(self.data)
        })
    }
}

因为不想限制池中管理对象的范围,所以这里只检查那些实现了PoolItem协议的对象是否能重用。

...
 let pitem = item as? PoolItem
 if (pitem == nil || pitem!.canReuse) {
       self.data.append(item)
       dispatch_semaphore_signal(self.semaphore)
 }
...

接着我们让Book类实现PoolItem协议:

import Foundation
class Book : PoolItem {
    
    let author:String
    let title:String
    let stockNumber:Int
    var reader:String?
    var checkoutCount = 0
    
    init(author:String, title:String, stock:Int) {
        self.author = author
        self.title = title
        self.stockNumber = stock
    }
    
   var canReuse:Bool {
        get {
            let reusable = checkoutCount < 5
            if (!reusable) {
                print("Eject: Book#\(self.stockNumber)")
            }
            return reusable
        }
    }
}

为了测试这个策略,我们必须修改图书馆的书的总数,以便让读者借到临界线。

Library.swift

...
   static let sharedInstance = Library(stockLevel: 5)
...

运行程序,得到下面结果:

Starting...
Eject: Book#5
Eject: Book#4
All blocks complete
...Book#2...
Checked out 3 times
In stock
...Book#1...
Checked out 3 times
In stock
...Book#3...
Checked out 4 times
In stock
There are 3 books in the pool

理解空对象池策略

空对象池策略,显而易见,就是对象池空了时发生请求时该怎么办。最简单的方法就是我们上面所用的,请求的线程一直等待直到有对象被归还。

阻塞策略虽然简单,但是如果池中的对象的总数和需求总数不协调的话就会拖慢应用程序。 如果阻塞策略和和前面提到的不信任管理策略结合在一起的话还会造成应用程序死锁。

我们做如下修改:

main.swift

import Foundation
var queue = dispatch_queue_create("workQ", DISPATCH_QUEUE_CONCURRENT)
var group = dispatch_group_create()
print("Starting...")
for i in 1 ... 35{
    dispatch_group_async(group, queue, {() in
        var book = Library.sharedInstance.checkoutBook("reader#\(i)")
        if (book != nil) {
            NSThread.sleepForTimeInterval(Double(rand() % 2))
            Library.sharedInstance.returnBook(book!)
        }
    })
}

dispatch_group_wait(group, DISPATCH_TIME_FOREVER)

print("All blocks complete")

Library.sharedInstance.printReport()

我们将借书的请求从20次增加到了35次,很显然我们会得到下面的结果:

Starting...
Eject: Book#4
Eject: Book#5
Eject: Book#3
Eject: Book#2
Eject: Book#1

实现请求失败策略

请求失败策略通过将责任转移给请求对象的组件来处理空对象池的情况。组件必须指定在请求失败之前的等待时间。

Pool.swift

...
    func getFromPool(maxWaitSeconds:Int = 5) -> T? {
       var result:T?
        
       let waitTime = (maxWaitSeconds == -1) ? DISPATCH_TIME_FOREVER : dispatch_time(DISPATCH_TIME_NOW, (Int64(maxWaitSeconds) * Int64(NSEC_PER_SEC)))
    
       if (dispatch_semaphore_wait(semaphore, waitTime) == 0) {
           dispatch_sync(queue, { () -> Void in
        
            if (self.data.count == 0 && self.itemCount < self.maxItemCount) {
                result = self.itemFactory()
                self.itemCount++
            } else {
                result = self.data.removeAtIndex(0);
            }
        })
        
        }
        return result
    }
...

Caution:DISPATCH_TIME_NOW 常量只能在dispatch_time方法中使用。如果你在其他地方使用,只会返回0。

我们将waitTime的值作为参数传递给了dispatch_semaphore_wait方法。

...
if (dispatch_semaphore_wait(semaphore, waitTime) == 0) {
...

dispatch_semaphore_wait 方法会一直阻塞直到returnToPool方法中的信号量发出信号或者超过指定的等待时间。

我们需要知道为什么信号量(semaphore)允许线程继续执行。如果是因为在对象池里有对象可用了,那么我们将获取对象并交给请求组件。如果是因为超过等待时间, 那么我们希望返回一个空值。

getFromPool方法已经是返回一个可选类型的Book对象。这就意味着Library 类已经设置好了应对获取对象失败的情况。

Library .swift

...
    func checkoutBook(reader:String) -> Book? {
        let book = pool.getFromPool()
        book?.reader = reader
        book?.checkoutCount++
        return book
    }
...

所以现在我们修改main.swift:

main.swift

import Foundation

var queue = dispatch_queue_create("workQ", DISPATCH_QUEUE_CONCURRENT)
var group = dispatch_group_create()
print("Starting...")
for i in 1 ... 35{
    dispatch_group_async(group, queue, {() in
        var book = Library.sharedInstance.checkoutBook("reader#\(i)")
        if (book != nil) {
            NSThread.sleepForTimeInterval(Double(rand() % 2))
            Library.sharedInstance.returnBook(book!)
        }else{
            dispatch_barrier_async(queue, { () -> Void in
                 print("Request \(i) failed")
            })
        }
    })
}

dispatch_group_wait(group, DISPATCH_TIME_FOREVER)

dispatch_barrier_sync(queue, {() -> Void in
        print("All blocks complete")
        Library.sharedInstance.printReport()
    })

如果没有获取到对象,我们就向控制台输出失败信息。因为print方法并不是线程安全的,所谓我们把它放进了GCD块中。这里我们用了 dispatch_barrier_async是因为是并发队列(DISPATCH_QUEUE_CONCURRENT)。
如果我们运行程序,会得到下面的输出:



处理枯竭的对象池

前面所介绍的空对象池策略的问题是当池中对象耗尽时它要求请求组件等待。但是我们看到所有的对象都已经因为过度使用被丢弃了,而对象池也没有能力创建新的对象。

Pool.swift

import Foundation

class Pool<T> {
    private var data = [T]()
    private let queue = dispatch_queue_create("arrayQ", DISPATCH_QUEUE_SERIAL)
    private let semaphore:dispatch_semaphore_t
    private var itemCount = 0
    private let maxItemCount:Int
    private let itemFactory: () -> T
    private var ejectedItems = 0
    private var poolExhausted = false
    
    init(maxItemCount:Int, factory:() -> T) {
        self.itemFactory = factory
        self.maxItemCount = maxItemCount
        semaphore = dispatch_semaphore_create(maxItemCount)
    }
    
    func getFromPool(maxWaitSeconds:Int = 5) -> T? {
       var result:T?
        
       let waitTime = (maxWaitSeconds == -1) ? DISPATCH_TIME_FOREVER : dispatch_time(DISPATCH_TIME_NOW, (Int64(maxWaitSeconds) * Int64(NSEC_PER_SEC)))
       if (!poolExhausted) {
         if (dispatch_semaphore_wait(semaphore, waitTime) == 0) {
             if (!poolExhausted) {
                dispatch_sync(queue){() -> Void in
        
                    if (self.data.count == 0 && self.itemCount < self.maxItemCount) {
                        result = self.itemFactory()
                        self.itemCount++
                    } else {
                        result = self.data.removeAtIndex(0);
                    }
                }
              }
            }
        }
        return result
    }

    
    func returnToPool(item:T) {
        dispatch_async(queue) { () -> Void in
           let pitem = item as? PoolItem
           if (pitem!.canReuse) {
              self.data.append(item)
              dispatch_semaphore_signal(self.semaphore)
           }else{
             self.ejectedItems++
             if (self.ejectedItems == self.maxItemCount) {
                self.poolExhausted = true
                self.flushQueue()
            }
           }
        }
        
    }
        
        private func flushQueue() {
            let dQueue = dispatch_queue_create("drainer", DISPATCH_QUEUE_CONCURRENT)
            var backlogCleared = false
            
            dispatch_async(dQueue) { () -> Void in
                dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER)
                backlogCleared = true
                
            }
            
            dispatch_async(dQueue) { () -> Void in
                while (!backlogCleared) {
                    dispatch_semaphore_signal(self.semaphore)
                }
                
            }
            
        }
        
        
    func processPoolItems(callback:[T] -> Void) {
        dispatch_barrier_sync(queue){ () in
            callback(self.data)
        }
     }
        
}

上面所做的修改提出了两个问题:识别对象池耗尽和在此期间拒绝任何请求。为了识别对象池什么时候耗尽,我们在returnToPool中记录被丢弃的对象数。而且当所有的对象都被丢弃时,我们将实例变量 poolExhausted的值设置为true并且调用了flushQueue方法。

同时我们修改了getFromPool方法,所以在等待信号量发出之前都会检查属性poolExhausted是否为false,这意味着如果对象池耗尽的时候来值组件的请求都将会立即返回。

第二个问题是处理那些刚好在对象池耗尽之前的请求。这些请求线程会一直等待知道GCD发出信号。很不幸的是,GCD信号量(semaphore)并没有提供一个可以唤醒所有等待线程的方法,所以这里我们通过flushQueue 方法间接的去实现。

这里我们创建了另一个队列并为它添加了两个执行块。第一个执行块等待信号量并且当信号发送时设置局部变量backLogCleared 为ture。

...
 dispatch_async(dQueue) { () -> Void in
                dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER)
                backlogCleared = true
                
            }
...

GCD信号量允许线程通过是先进先出原则(FIFO),这就意味着只有当所有在对象池刚好耗尽之前等待的线程允许通过后,这个执行块线程才会允许被通过。

第二个执行块重复的向信号量发信号直到backLogCleared的值改变。

...
  dispatch_async(dQueue) { () -> Void in
           while (!backlogCleared) {
                dispatch_semaphore_signal(self.semaphore)
              }
   }
...

这里增加执行块的队列是并发的,并且信号量会发出通知直到所有积压的线程通过后。我想阻止那些等到信号量的请求去执行修改数组的代码,所以在信号量发出后也对poolExhausted的值做了检查。

...
 if (!poolExhausted) {
         if (dispatch_semaphore_wait(semaphore, waitTime) == 0) {
             if (!poolExhausted) {
...

我们将maxWaitSeconds参数的值改为-1来测试。只有当对象池耗尽的时候请求才会被拒绝,而且结果也和上面等待5秒的例子相似,但是这种实现阻止了当请求等待时间无上限并且对象池耗尽时请求线程被锁住的问题。下面是执行结果:



创建一个灵活的对象池

如果对象池有能力创建对象满足需求,那么就没有必要拒绝组件的请求。在软件开发方面,灵活的对象池可以一般用在那种需要创建额外的对象来满足增长的需求的情况。

在现实中,为了满足最高需求通常图书馆会从最近的分馆借书。这并不是理想的解决方案,但也意味着借书的人不用在长时间的等待了。请看下面例子:

BookSource.swift

import Foundation

class BookSeller {
    class func buyBook(author:String, title:String, stockNumber:Int) -> Book {
        return Book(author: author, title: title, stock: stockNumber)
    }
}

class LibraryNetwork {
    class func borrowBook(author:String, title:String, stockNumber:Int) -> Book {
        return Book(author: author, title: title, stock: stockNumber)
    }
    
    class func returnBook(book:Book) {
        // do nothing
    }
}

接着我们修改对象池类:

import Foundation

class Pool<T> {
    private var data = [T]()
    private let queue = dispatch_queue_create("arrayQ", DISPATCH_QUEUE_SERIAL)
    private let semaphore:dispatch_semaphore_t
    
    private let itemFactory: () -> T
    private let peakFactory: () -> T
    private let peakReaper:(T) -> Void
    
    private var createdCount:Int = 0
    private let normalCount:Int
    private let peakCount:Int
    private let returnCount:Int
    private let waitTime:Int
    
    init(itemCount:Int, peakCount:Int, returnCount: Int, waitTime:Int = 2,
        itemFactory:() -> T, peakFactory:() -> T, reaper:(T) -> Void) {
        self.normalCount = itemCount
        self.peakCount = peakCount
        self.waitTime    = waitTime
        self.returnCount = returnCount
        self.itemFactory = itemFactory
        self.peakFactory = peakFactory
        self.peakReaper  = reaper
        self.semaphore   = dispatch_semaphore_create(itemCount)
    }
    
    func getFromPool() -> T? {
        var result:T?
        
        let expiryTime = dispatch_time(DISPATCH_TIME_NOW,(Int64(waitTime) * Int64(NSEC_PER_SEC)))
        
        if(dispatch_semaphore_wait(semaphore, expiryTime) == 0 ){
            dispatch_sync(queue, { () -> Void in
                if(self.data.count == 0){
                    result=self.itemFactory()
                    self.createdCount++
                }else{
                    result = self.data.removeAtIndex(0)
                }
            })
        }else{
            dispatch_sync(queue, { () -> Void in
                result = self.peakFactory()
                self.createdCount++
            })
        }
     
        return result
    }

    
    func returnToPool(item:T) {
        dispatch_async(queue) { () -> Void in
           if (self.data.count>self.returnCount && self.createdCount>self.normalCount) {
              self.peakReaper(item)
              self.createdCount--
           }else{
             self.data.append(item)
             dispatch_semaphore_signal(self.semaphore)
            }
           }
        }
    
        
    func processPoolItems(callback:[T] -> Void) {
        dispatch_barrier_sync(queue){ () in
            callback(self.data)
        }
     }
        
}

最后我们修改Library类:

...
    private init(stockLevel:Int) {
        var stockId = 1
        pool = Pool<Book>(
            itemCount:stockLevel,
            peakCount: stockLevel * 2,
            returnCount: stockLevel / 2,
            itemFactory: {() in
            return BookSeller.buyBook("Dickens, Charles",
            title: "Hard Times", stockNumber: stockId++)},
            peakFactory: {() in
            return LibraryNetwork.borrowBook("Dickens, Charles",
            title: "Hard Times", stockNumber: stockId++)},
            reaper: LibraryNetwork.returnBook
        )
    }
...

运行程序,得到一下输出:



理解分配策略

分配策略决定了如何选择一个可用对象去满足一个请求。到目前为止,我们所用的是先进先出策略(FIFO),将数组像队列一样使用。这个策略的优点是使用简单,但同时也意味着对象会被不均衡的分配,某些请求成功次数会多余其他请求。

对于大多数应用来说,先进先出策略都会很合适,但是有些应用也会要求一些不同的分配策略。下面我们将返回最少利用次数的对象。

Pool.swift

import Foundation

class Pool<T> {
    private var data = [T]()
    private let queue = dispatch_queue_create("arrayQ", DISPATCH_QUEUE_SERIAL)
    private let semaphore:dispatch_semaphore_t
    
    private let itemFactory: () -> T
    private let itemAllocator:[T] -> Int
    private let maxItemCount:Int
    private var createdCount:Int = 0
    
    init(itemCount:Int, itemFactory:() -> T, itemAllocator:([T] -> Int)) {
        self.maxItemCount = itemCount
        self.itemFactory = itemFactory
        self.itemAllocator = itemAllocator
        self.semaphore = dispatch_semaphore_create(itemCount)
    }
    
    func getFromPool(maxWaitSeconds:Int = -1) -> T? {
        var result:T?
        
        if (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER) == 0){
            dispatch_sync(queue, { () -> Void in
                if (self.data.count == 0) {
                    result = self.itemFactory()
                    self.createdCount++
                }else{
                    result = self.data.removeAtIndex(self.itemAllocator(self.data))
                }
            })
        }
     
        return result
    }

    
    func returnToPool(item:T) {
        dispatch_async(queue) { () -> Void in
            self.data.append(item);
            dispatch_semaphore_signal(self.semaphore)
        }
        
    }
        
    
    func processPoolItems(callback:[T] -> Void) {
        dispatch_barrier_sync(queue){ () in
            callback(self.data)
        }
     }
        
}

这是一个延迟创建对象并且固定大小的对象池。我们定义了一个初始化参数itemAllocator,它是一个接受数组为参数的闭包,这个闭包返回数组中选定对象的位置。
接着我们修改Library类:

Library.swift

...
    private init(stockLevel:Int) {
        var stockId = 1
        pool = Pool<Book>(
        itemCount:stockLevel,
        itemFactory: {() in
            return BookSeller.buyBook("Dickens, Charles",
            title: "Hard Times", stockNumber: stockId++)},
            itemAllocator: {( books) in return 0 }
        )
    }
...

这时闭包返回的是0,也就是先进先出策略,运行程序

我们再做修改,实现最少使用策略:

Library.swift

...
    private init(stockLevel:Int) {
        var stockId = 1
        pool = Pool<Book>(
        itemCount:stockLevel,
        itemFactory: {() in
            return BookSeller.buyBook("Dickens, Charles",
            title: "Hard Times", stockNumber: stockId++)},
        itemAllocator: {(books) in
            var selected = 0
            for index in 1 ..< books.count {
                if (books[index].checkoutCount < books[selected].checkoutCount) {
                    selected = index
                }
            }
            return selected}
        )
    }
...

此时运行程序,可以看出结果比FIFO策略更佳均衡:



最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,873评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,483评论 1 306
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,525评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,595评论 0 218
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,018评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,958评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,118评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,873评论 0 208
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,643评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,813评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,293评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,615评论 3 262
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,306评论 3 242
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,170评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,968评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,107评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,894评论 2 278

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,644评论 4 59
  • 次日清晨。 “子默哥哥,救救我,不要丢下我……” 睡梦中,苏瞳身体蜷缩着,嘴里不停的喃喃自语,她的眉头紧蹙,额头上...
    安若阡陌阅读 292评论 0 0
  • 我们在准备发起咖啡店和健康管理项目,还没有完全制定出完整的合理的项目发起方案。(一直再完善)核心发起人已经选定四五...
    7f8515619d98阅读 192评论 0 1
  • 各位书友,周五早安!今天我们继续共读《掌控》第八章:以弱胜强的掌控术,让你搞定那些难缠的人,请阅读p219-p24...
    热点榜中榜阅读 20,972评论 1 7
  • 逝长烟悲途,几经零落,渐次何来甘味,去自苦多,忧愁日,少年时,且纵胡驹良马,好生驱驰。 画舫又成空,凭栏对日,叹曲...
    林下杉杉阅读 214评论 1 5