0

I have a data structure which I personally implemented that now needs to be used across multiple threads.

typedef struct 
{
    void** array_of_elements;
    size_t size;
} myStruct;

For simplicity, let's say my data structure has these functions:

// Gets a data element from the structure.
void* get(myStruct *x);
// Prints out all the data elements.
void print(myStruct *x);
// Adds an element into the structure.
void add(myStruct *x, void *to_be_added);

It's not a problem whatsoever to call get while another thread is calling print since they are both accessors. However, get and print cannot work while add is currently being called. Vice versa, add cannot work if get and print are currently in-progress.

So I changed myStruct to look like the following:

typedef struct 
{
    void** array_of_elements;
    size_t size;

    // True when a mutator is editing this struct.
    bool mutating;
    // The number of threads currently accessing this struct.
    int accessors;
} myStruct;

Now my functions look like the following:

void* get(myStruct *x)
{
    // Wait for mutating to end.
    while (x->mutating);
    // Indicate that another accessor is now using this struct.
    x->accessors++;

    // get algorithm goes here

    // Declare we are finished reading.
    x->accessors--;

    return ...
}

// Same as above...
void print(myStruct *x)
...

void add(myStruct *x)
{
    // Wait for any accessors or mutators to finish.
    while (x->mutating || x->accessors > 0);    
    x->mutating = true;

    // add algorithm here

    x->mutating = false;
}

BUT, I think there are a lot of problems with this approach and I can't find a way to solve them:

  • One of my classmates told me using while loops like this slows the thread down immensely.
  • It has no sense of a queue. The first method that begins waiting for the myStruct to finish being used isn't necessarily the one that goes next.
  • Even IF I had a queue data structure for which thread goes next, that data structure would also need to be synchronized, which in itself is an infinite loop of needing a synchronized data structure to synchronize itself.
  • I think it's possible that in the same nano second one thread changes the accessors counter from 0 to 1 (meaning they want to start reading), it's possible for a mutator thread to see it's value is 0 and start mutating. Then, both a mutator thread and an accessor thread would be going at the same time.
  • I'm pretty sure this logic can cause grid-lock (threads waiting on each other infinitely).
  • I don't know how to make certain threads sleep and wake up right when they need to for this task, besides having it stuck in a while loop.
Hatefiend
  • 3,416
  • 6
  • 33
  • 74
  • Look into read-write locks... – Dmitri Apr 12 '17 at 02:36
  • @Dmitri Is what I have for read/write locks not even remotely close of what the real implementation would look like? – Hatefiend Apr 12 '17 at 02:39
  • If you're sharing a variable among threads, you should be using atomic accesses or some sort of synchronisation mechanism like a mutex. A read-write lock is like a mutex, except that it allows multiple threads to read, but requires exclusive access for writing. For posix threads, there's `pthread_rwlock_t`, and windows has "slim reader writer locks" – Dmitri Apr 12 '17 at 02:46
  • you need an atomic check and mutate, your version multiple threads could check mutating and it be false, and all move into setting the mutating variable all at the same time – Keith Nicholas Apr 12 '17 at 02:47
  • @Dmitri I understand threads contain mutexes but isn't it my **structure itself** that needs a mutex or lock? Like, inside my `struct` there must be some variable of some sort that handles that right? – Hatefiend Apr 12 '17 at 02:48
  • You acquire (lock) the mutex/rwlock/etc. before accessing a resource, and release (unlock) when you're done... as long as *all* code that accesses it does this, the resource is protected. It doesn't matter whether it's part of the structure or not, as long as you use it when accessing the structure. – Dmitri Apr 12 '17 at 02:51
  • @KeithNicholas Yeah I definitely understand that. How does one implement an 'atomic check and mutate' ? – Hatefiend Apr 12 '17 at 06:04

1 Answers1

0

You have the right idea, just the wrong approach. I'm not sure what OS you're programming on, but you want to look at the concepts of mutex or semaphore to do what you want to do.

On Linux/Unix that is POSIX compliant, you can look at pthreads:

http://www.cs.wm.edu/wmpthreads.html

On Windows, you can look at Critical Sections for something close to a mutex concept:

https://msdn.microsoft.com/en-us/library/windows/desktop/ms682530(v=vs.85).aspx

Or WaitForMultipleObjects for something close to a semaphore:

https://msdn.microsoft.com/en-us/library/windows/desktop/ms687025(v=vs.85).aspx

And, yes, using while loops are a bad idea. In this case, you are using what is known as a busy loop. More reading on it here:

What is a busy loop?

Using mutex or semaphore, no while loop is required. Good luck!

Community
  • 1
  • 1
  • So the implementation is library dependent? That doesn't seem right. I thought inside of my `struct`, I need to add some variables to lock my threads since my `struct` **is the mutex**? – Hatefiend Apr 12 '17 at 04:06
  • Let me restate: is there any way to do this without use of any thread libraries? The developer that uses my structure may not be on the operating system I design it for. So instead, can I just have a variable inside my `struct` that dictates when it is or is not safe to use that `struct` ? – Hatefiend Apr 12 '17 at 04:30
  • @Hatefiend if you don't want to tie your code to any particular thread API, it may be better to leave the locking up to the user rather than trying to implement your own one-off mutex or read-write lock (which may be more difficult to do correctly than you expect). All they'd need to do is acquire/release the lock of their choice (presumably one that goes with whatever threading implementation they're using) before/after calling your access functions. – Dmitri Apr 12 '17 at 04:45
  • @Hatefiend *is there any way to do this without use of any thread libraries?* Yes. You can reimplement what the thread libraries already do. There's a reason those libraries exist... – Andrew Henle Apr 12 '17 at 10:30
  • @AndrewHenle It's just that I really hate having to learn new libraries and I'll avoid it at all costs. External libraries require a ton of hassle to download, link with visual studio, lot of reading and after finally learning how it all works, there might be a better library out there or maybe your library isn't cross platform so now your amazing cross platform project is windows only, etc. I do like the idea of the user providing the locking/unlocking mechanism though. – Hatefiend Apr 12 '17 at 10:53
  • @Hatefiend If you're on Windows and using Visual Studio, you likely won't have to link any new libraries. You just have to add a #include to your source file. Most C++ projects in visual studio already link the Windows API library. –  Apr 13 '17 at 02:08