Lock structure
CAS, qspinlock and kmob can share the same lock structure:
1 | struct qspinlock { |
Since pending & locked requires only 1 bit, there’s space for more information (if needed).
The threads could be spinning/waiting at:
- CAS spins on the lock (32bits)
- qspinlock:
- uncontended: lock (32bits)
- pending: locked (8bits)
- uncontended queue: locked_pending (16bits)
- contended queue: node.locked (and later locked_pending)
- komb:
- fastpath: lock (32bits)
- midpath: locked_pending (16bits)
- node.wait (and later lock)
For the nodes spinning on the lock itself, no further notification needed.
For those waiting on their own node, will need to notify according to queue type.
Node structure
TODO: How to store node?
- Copy all fields inside a single sqnode structure. Drawback: Will need to modify all underlying lock & unlock functions
1 | struct sqnode{ |
- Put MCS node and KOMB node inside sqnode. Drawback: Additional handling of getting next node
1 | struct sqnode{ |
Passing sqnode instead of mcs/komb nodes
Modify select_next_waiter(sqnode)
Plan 2 seems to be better?
Lock & Unlock
1 | # Indicate current lock type |
For KOMB phase 4:
Check if next node is KOMB and next.next is not none
1 |
|
For qspinlock, waiting on QLOCKED_PENDING_MASK also implies waiting on QLOCKED_COMBINER_VAL
Analysis
Switching from QSPINLOCK to KOMB
node A[SPIN] -> node B[SPIN] -> node C[KOMB] -> node D[KOMB] -> node E[KOMB]
A holds the lock
- B spins locked_pending
C spins on node.lockedC spins on lock.glock since qprev is None- D spins on node.locked
A unlock, set locked to 0
B gets the lock, (0,1,1)->(0,1,0)->(0,0,1)
B unlock, notify C by setting C.node.locked to 0B unlock, sets val to (0,0,0)C gets the lock, switched
Switching from KOMB to QSPINLOCK
A[KOMB] -> B[KOMB] -> C[KOMB] -> D[KOMB] -> E[SPIN]
A holds the lock, and doing combining
- execute B’s CS
- execute C’s CS
- notify D
- execute A’s CS
- unlock
D spins on D.qsnode.locked
- D gets lock
- notify E
- unlock
E spins on E.qsnode.locked
- E gets lock, switched