Hello world ! Here is the situation in which I am:
I have a certain list of objects, that will evolve during runtime. So I store them in a seq or a table. let s: seq[MyObj]
Can a change in one object cause a global reallocation of the whole seq ? If it could, then My plan to prevent that is to replace a seq of MyObj with a seq of pointers to MyObj (through dynamic allocation). Can you suggest better ideas ?
Several threads of my program will need to read and write to my objects simultaneously. I would like to place a lock on each object rather than on the global seq, to make things go faster. But how can I achieve that ??
Can a change in one object cause a global reallocation of the whole seq ?
No, it cannot.
Sorry for insisting. Is there a way to guard a sequence item with a lock ?
It would be a waste of time if all my threads had to wait for the same lock (that of the entire seq) to access objects that often don't interact with each other.
Thanks in advance.
If you make a seq of objects, you should be able to access the individual elements of the seq by their raw pointer address from your threads, and put a lock in these individual elements for fine grained locking of their contents.
From your different threads perspectives, ignore the fact that these objects are stored in a seq, just handle them as an array of objects with a fixed, for which the first member lives at s[0].addr. Make sure not to share the ref itself among the threads, only the raw ptr address of the 1st element. Also, do make sure the seq itself gets referenced somewhere in the main thread to prevent it from being destructed when you are not looking.
put a lock in these individual elements
The only way to attach a lock to a memory space, that I know, is to add a pragma in a variable declaration:
var num {.guard: lock}: int
How can i use them on dynamically allocated items ?
No, the guard is not what you are looking for; you will need to put a regular mutex in your object and manually lock/unlock it when you are accessing your data.
The .guard informs the Nim compiler about your intentions with regards to locking, it can help you protect your data by warning you that you are accessing a guarded memory location without taking the lock.
This is one way to do it. There are probably nicer ways, but this is pretty explicit to show what is happening.
import std/locks
type
Thing = object
val: int
lock: Lock
ThingInfo = object
thingPtrs: ptr UncheckedArray[Thing]
count: int
ThingThread = Thread[ThingInfo]
# The thread function running in N threads; this will increment
# 'val' on each thing a number of times.
proc threadFn(info: ThingInfo) {.thread,gcsafe.} =
for i in 0..<1000:
for j in 0..<info.count:
# Get a raw ptr to the thing number `j`
let thing: ptr Thing = info.thingPtrs[j].addr
# Lock the thing and update its value
withLock thing.lock:
inc thing.val
# Thing constructor
proc newThing(): Thing =
result.val = 0
result.lock.initLock()
proc main() =
# Fill a seq with four Things
var thingSeq: seq[Thing]
var thingCount = 4
for i in 0..<thingCount:
thingSeq.add newThing()
# Store the info on where to find the things, by raw ptr
var thingInfo = ThingInfo(
thingPtrs: cast[ptr UncheckedArray[Thing]](thingSeq[0].addr),
count: thingCount
)
# Create two threads working on things
var thread1, thread2: ThingThread
createThread(thread1, threadFn, thingInfo)
createThread(thread2, threadFn, thingInfo)
# Wait for the threads to finish. Do not lose the ref to the seq here!
thread1.joinThread()
thread2.joinThread()
# Verify result
var sum = 0
for t in thingSeq:
sum.inc t.val
doAssert sum == 8000
main()
Worth noting the guard clause can be used on fields.
type
Thing = object
val {.guard: Lock.}: int
lock: Lock