🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Multithreading with mutexes and time constraints

Started by
5 comments, last by Shaarigan 5 years, 3 months ago

Hi everyone,

I am writing a server and I need to generate chunks of the world and access them in other threads. The initialization may take a while (seconds), but I need the other threads to be faster than that (basically realtime).

My idea is to use multiple mutexes per chunk, an InitMutex and a ModifyMutex.

When initializing (pseudocode):


Lock InitMutex;
Initialize the chunk;
Lock ModifyMutex;
Set status "initialized";
Unlock both mutexes;

When reading/modifying:


Lock ModifyMutex;
If status is "initialized":
    Perform operations on chunk data;
Unlock ModifyMutex;

Does this work? Will the thread that only locks the ModifyMutex see the data written by the initialization thread?

In the standard it says:

Quote

All lock and unlock operations on the mutex follow a single total order, with all visible effects synchronized between the lock operations and previous unlock operations on the same object.

Does that mean that the initialization of the chunk data is synchronized when ModifyMutex is locked on the modifying thread because it occured before ModifyMutex was unlocked?

Cheers,

Magogan

Advertisement

Yep, this is safe. Because you lock the mutex before writing the initialisation flag (after writing the data), all of that data will be safely visible to the other threads that later lock that mutex.

However, you don't actually need the initialisation mutex here at all, because no other threads will attempt to read the object until they've successfully locked the mutex and read the initialisation flag. The init thread can just write the data, then lock the mutex, write the init flag and then unlock the mutex.

Okay, thanks.

The example is a little bit simplified. I need the initialization mutex because multiple threads may try to initialize the chunk at the same time. And the chunks get deleted when they are not used, so I need to make sure to not delete them while they are being initialized (which could in theory happen if the initialization thread is inactive for a while or a player disconnects or whatever).

Funny, I'm currently working on similar things you are. i.e. generating world chunks in threads. 

Sound good, I use multithreading in my game for chunk generation as well

Depending on how the game world is setup you probably want to have good preloading scheme as well, just to make sure the chunks are ready and loaded well in advance for when you need them.

Concerning deleting the chunks, one suggestion (which may/or may not apply depending on you game) is to not delete the unused chunks immidiately. At least of the player could travel back and use them again. Unless they are really huge in memory you would most likely afford to keep a couple on the heap for fast access again

I sorted the unused chunks by accesstime, if the game reaches the maximum allowed buffered chunks not in use, it start replacing the ones that have the longest time since they were accessed. A rough guess that they are more unlikely to be needed again soon

Currently making BorderStrain, 2D Sandbox ARPG www.BorderStrain.com

Depending on the time your initial thread takes to write the chunk, you can use a read/write spin lock or any other form of read/write lock. The lock allowes multiple readers to access the data at the same time while a single writer blocks until all readers have successfully leaved the section and prevents newly readers from entering the section until the writer has left.


WriteMask = 0x7ffffff
ReaderMask = 0x8000000

//Acquire read access
while true
    while (Lock & ReaderMask) != 0
        sleep
    end

    OldLock = (Lock & WriteMask)
    NewLock = OldLock + 1

    if CompareExchange(Lock, NewLock, OldLock) == OldLock
        return
    end
end

//Return read access
Decrement(Lock)

//Aquire write access
while true
    while (Lock & ReaderMask) != 0 //<- waits until writes have finished
        sleep
    end

    OldLock = (Lock & WriteMask)
    NewLock = (OldLock | ReaderMask)

    if CompareExchange(Lock, NewLock, OldLock) == OldLock
        while (Lock & WriteMask) != 0 //<- waits until readers have left the section
            sleep
        end

        return
    end
end
  
//Return write access
Lock = 0

I use this in a database locking data records for read/write access from multiple threads.

Instead of sending threads to sleep you could manage server side tasks in a pool and let spinning threads instead execute those tasks until they got the lock they desired

This topic is closed to new replies.

Advertisement