3 ms·
That code executes 4 times. That's not how you do things in Swift: class Dwizzle { private let countQueue = DispatchQueue(label: "hey debugger! I'm here
by Tanegashima 10y ago
That code executes 4 times.
That's not how you do things in Swift:
class Dwizzle {
private let countQueue = DispatchQueue(label: "hey debugger! I'm here!")
private var count = 0 //type inference to Int
func fizzle() {
countQueue.sync {
if count > 3 { return }
count += 1
}
}
}
See, no need for mutexes, or defers, or that lock_guard.
Just simple closures you already know.
Now you want to expose the count variable to other types, always in a non-negotiable thread-safe manner?
private let countQueue = DispatchQueue(label: "hey debugger! I'm here!")
private var _count = 0
public var count : Int {
get {
return countQueue.sync {
return _count
}
}
set(newValue){
if newValue >= 3 {
self.countQueue.sync {
self.count = newValue
}
}
}
}
And maintain the rest.
Now what if you want to clean up, and need to only one fizzling executing at a time in the whole process? Doesn't matter which process calls the fizzling?
class Dwizzle {
private static let countQueue = DispatchQueue(label: "hey debugger! I'm here!")
private var _count = 0
public var count : Int {
get {
return Dwizzle.countQueue.sync {
return _count
}
}
set(newValue){
if newValue >= 3 {
Dwizzle.countQueue.sync {
self.count = newValue
}
}
}
}
func fizzle() {
if count > 3 { return }
count += 1
}
}
To me, your code looks like a semaphore, let me introduce you to my over-the-knee fully-functional semaphore that can implement your Dwizzle class:
class HackerNewsSemaphore {
private let queue = DispatchQueue(label: "sem queue")
private var count = 0
func up(){
queue.sync {
count += 1
if executeWhenUp && onlyExecuteWhen(count) {
execute(count)
}
}
}
func down(){
queue.sync {
count -= 1
if count > 0 && executeWhenDown && onlyExecuteWhen(count) {
execute(count)
}
}
}
var executeWhenUp : Bool
var executeWhenDown : Bool
var onlyExecuteWhen : (Int)->Bool
var execute : (Int)->Void
init(whenTrue: @escaping (Int)->Bool, whenUp: Bool, whenDown: Bool, execute: @escaping (Int)->Void) {
self.onlyExecuteWhen = whenTrue
self.executeWhenUp = whenUp
self.executeWhenDown = whenDown
self.execute = execute
}
}
//test
let dwizzle = HackerNewsSemaphore(whenTrue: { (i) -> Bool in return i <= 3},
whenUp: true,
whenDown: true,
execute: { i in print("fizzle \(i)") })
for x in 0...10 {
dwizzle.up()
}
for x in 0...10 {
dwizzle.down()
}
//output:
fizzle 1
fizzle 2
fizzle 3
fizzle 3
fizzle 2
fizzle 1
- jsolson 10y agoI appreciate the GCD approach, and for many problems it'd absolutely be what I'd suggest and use myself. My little Dwizzle toy could certainly be built that way. For concrete systems programming tasks that deal with things on a per-IOP basis, for example, it's specified semantics are insufficient make strong performance assertions. It's not a blocking semaphore -- we explicitly don't want it to block after the third call. The example was an entirely artificial construction to get (a) an early return and (b) a use for locking for thread safety (you could of course also do it with just an opportunistic CAS loop). That said, yes you could build it with a semaphore that included tryDown() in its contract. My day job is working on the VMM that backs Google Compute Engine. Thread hops (it's the wakeups, really) have a measurable and for some workloads substantial performance and efficiency penalty. This makes the use of GCD and similar technologies untenable (while GCD could be written such that dispatch queues handle synchronous dispatches on the calling thread, the docs do not guarantee those semantics iirc). To really drive the point home Hypervisor.framework requires that some calls be made from specific threads, so taking on a similar project to my day job in Swift would really absolutely require knowing details about execution context. I've been building a little toy VMM, mostly as an opportunity to learn Swift outside of work, so my concerns aren't entirely hypothetical (for a null or nearly-null operation like fizzle GCD is up to a hundred times slower in Swift today than a spinlock lock/unlock with warm caches). This still puts dispatch in the realm of microseconds, though, so for many workloads it's fine. It's too long (and way too many cycles spent in actual compute) for things like dispatching network packets or flash storage IOs, though, particularly if overall cycle budget is a concern.