FBB::Sh...ndition(3bobcat)

πŸ“¦
version 6.07.01 Β· 5 versions
, in debian trixie, ubuntu questing

πŸ’– FBB::Sh...ndition β€” the 3bobcat manual, bestie edition πŸ’–

okay so. you're here for FBB::SharedCondition and honestly? iconic of you. that's the Shared Memory Condition Variable πŸ’… and it's lowkey one of the most useful little gremlins in bobcat. this is part 1 of the whole page, so buckle up, we're just getting started πŸŽ€


✨ SYNOPSIS ✨

#include <bobcat/sharedcondition>

Linking option: -lpthread, -lbobcat


🌸 DESCRIPTION 🌸

so condition variables?? they're the thing you use to synchronize threads based on the values of data. that's it, that's the whole vibe. a condition variable lets threads wait until a certain condition has occurred, and then the threads go on with their little lives. the whole point is that waiting threads don't have to continuously poll the state of a variable β€” because polling would mean the thread has to gain access to the variable before it can even inspect its value, and that's just exhausting 😭. with condition variables, waiting threads literally just wait until they get notified. periodt.

SharedCondition objects can be used in combination with shared memory. a SharedCondition object interfaces to objects (called Condition objects in this man-page, iykyk) which are defined in shared memory and which contain a SharedMutex and a shared condition object. these Condition objects may be accessed by threads running in different processes. and those different processes might each run a single main thread, or they themselves can be multi-threaded. no cap.

condition variables get used in situations like these:

  • There exists a thread which should be suspended until a certain condition has been met.
  • This thread locks a mutex (or waits until the lock has been obtained)
  • While the condition hasn't been met, the thread is suspended (i.e., waits), automatically releasing the mutex's lock.
  • Somehow (see below) the thread is resumed, at which point the thread has automatically reacquired the lock.
  • Once the condition has been met, the while loop ends, and the mutex's lock is released.
  • There exists a second thread, which influences the variables that are elements of the condition, and which may notify the waiting thread, once the required condition has been met.
  • This second thread locks the same mutex as used by the first thread.
  • The second thread modifies the variables that are involved, and if the required condition has been met, it notifies the first thread.
  • The second thread releases the mutex's lock, allowing the first thread to obtain the mutex's lock.
⚠️ okay important one, bestie, do not skip this: while that first thread is waiting, it is suspended. it may be resumed when it receives a notification from another thread β€” but also for spurious reasons. so the first thread must verify that the condition has been met after resuming its actions. the way that a wake-up is not automatically a "yes the condition is true" is so real. check it yourself. every time. πŸ™ˆ

since condition variables are always used in combination with a mutex, SharedMutex encapsulates all the mutex-handling for you. the software using SharedCondition objects doesn't have to handle the mutex itself. you're welcome, we love a class that does the heavy lifting 🫢

SharedCondition objects are used to synchronize actions by different processes, using shared memory as their vehicle of synchronization/communication. the actual condition variable that is used by a SharedCondition object is defined in shared memory. and the SharedCondition objects themselves? they're small objects β€” they just contain the necessary information to access the actual shared memory condition variable. tiny but mighty, main character energy ⭐


πŸ¦‹ NAMESPACE πŸ¦‹

FBB

All constructors, members, operators and manipulators, mentioned in this man-page, are defined in the namespace FBB.


πŸ‘‘ INHERITS FROM πŸ‘‘

SharedMutex(3bobcat)


πŸŽ€ CONSTRUCTORS, DESTRUCTOR πŸŽ€

  • SharedCondition() β€” the default constructor creates an empty stub which cannot yet be used (or an FBB::Exception is thrown). but here's the slay: as the SharedCondition class supports assignment operators, empty stubs can easily be (re)configured at any time after their construction. build it empty, dress it up later πŸ’…
  • ~SharedCondition() β€” the class's destructor releases (if applicable) its lock on the shared condition variables mutex lock. and if its object is an empty stub? the destructor takes no action. it's giving "if you don't have anything to release, don't do anything" 🌈

Default, copy, and move constructors as well as the copy and move assignment operators are available. ✨


more parts coming, don't go anywhere πŸ“

πŸ’… MEMBER FUNCTIONS πŸ’…

okay so here's the tea, bestie: every single one of these member functions leaves the offset of the SharedMemory object (the one where the condition variable lives) exactly the way it found it. No shifting, no sliding. Internally it's like β€” save the current offset, do the thing you asked for, put the original offset back. So you can call these whenever, wherever, and not even think about what the SharedMemory's current offset is. Iconic behaviour honestly. πŸ‘‘

  • void lock() const β€” when this one comes back to you, your current process has locked the SharedCondition object. be careful though β€” do not call lock twice in the same thread of execution (peep sharedmutex(3bobcat) if you want the whole saga).
  • void notify() noexept β€” exactly one of the threads waiting on the SharedCondition object wakes up. The thread that called notify should drop its mutex lock shortly after, so the notified thread can actually grab that lock. A lil prototypical pseudo code moment showing how notify gets used:
sharedCondition.lock();     // lock the mutex
...                         // operate on the condition's variables
if (conditionWasMet)        // ready to notify
    sharedCondition.notify();
sharedCondition.unlock();   // release the lock

Since that sharedCondition.lock ... sharedCondition.unlock sequence might itself run in totally different flow-of-control sections, unlock can't be called from inside notify. Just how it is. πŸ™ˆ

  • void notifyAll() noexept β€” unlike plain old notify, this one wakes up all the threads waiting on the SharedCondition object. But here's the catch: once the current thread releases its mutex lock, only one of those signaled threads actually gets the lock. The pseudo code for notifyAll is the same as the pseudo code for notify (except obviously you're calling notifyAll, bestie).
  • std::streamsize offset() const β€” hands you back the location of the shared condition variable, measured inside the SharedMemory object. The shared condition object ends at offset() + SharedCondition::width(), which you'll meet down below. πŸ“
  • void unlock() const β€” releases the object's lock. If the current object doesn't have the object's lock, literally nothing happens. Chill.
  • void wait() β€” before you call wait, the current thread should already have obtained a lock on the SharedCondition object. fr fr.

When you call wait, the running thread suspends its whole vibe and waits until it gets notified. Once notified, it reacquires the lock and continues on its merry way. Shortly after that, the process should release its lock on the SharedCondition object again. lock. A prototypical pseudo code moment for wait:

sharedCondition.lock();         // lock the mutex
while (conditionWasNotYetMet)   // waiting required
    sharedCondition.wait();
...                             // do something: we have the lock
sharedCondition.unlock();       // release the lock
  • void wait(Predicate pred) β€” this one was implemented as a member template. Predicate is either a predicate function or a predicate function object. The predicate function β€” or the predicate function object's function call operators β€” may not require arguments. As long as pred keeps returning false, wait() (the no-arguments one) gets called. The function returns once pred has returned true.

The running thread should have obtained a lock on the SharedCondition condition variable before calling this member, and should release the lock after this member has returned.

The pseudo code for wait(pred) is identical to the pseudo code for wait (just with pred passed to wait, obviously πŸ’…).

  • std::cv_status wait_for(std::chrono::duration<Type, Unit> const &relTime) β€” member template alert 🚨. Type defines the type of the variable holding the amount of time (usually int64_t), specified in time unit Unit. Predefined duration types live in the std::chrono namespace, like std::chrono::seconds(4) (that's 4 seconds) or std::chrono::milliseconds(30) (that's 30 milliseconds).

The running thread should have obtained a lock on SharedCondition before calling this member, and should release the lock after this member has returned.

This member acts like wait, returning std::cv_status::no_timeout if a notification arrived before relTime passed. Otherwise you get std::cv_status::timeout back.

A prototypical pseudo code moment for wait_for:

sharedCondition.lock();         // lock the mutex
while (conditionWasNotYetMet)   // waiting required
{
    while (sharedCondition.wait_for(someTime)
           == std::cv_status::timeout)
        handle_timeout

    do_something
}
sharedCondition.unlock();       // release the lock

When wait_for returns, the current thread has obtained the shared condition's lock β€” but maybe that's because of a timeout 😭. You can verify by inspecting wait_for's return value, and pick an appropriate action from there.

  • bool wait_for(std::chrono::duration<Type, Unit> const &relTime, Predicate pred) β€” also implemented as a member template. Type defines the type of the variable holding the amount of time (usually int64_t), specified in time unit Unit. Predicate is either a predicate function or a predicate function object. The predicate function β€” or the predicate function object's function call operators β€” may not require arguments.

The running thread should have obtained a lock on SharedCondition before calling this member, and should release the lock after this member has returned.

As long as pred returns false, wait_for(relTime) gets called. If that function returns std::cv_status::timeout, then pred is called and its return value is what you get back. Otherwise true is returned.

The pseudo code for this member is identical to the pseudo code for the wait_for member above (just with pred also passed to wait_for, of course).

  • std::cv_status wait_until(std::chrono::time_point<Clock, Duration> const &absTime) β€” implemented as a member template. Clock defines the clock-type to use (usually std::chrono::system_clock), and Duration is the type name of a duration type (same kind of thing you'd use with wait_for). For example, to specify 5 seconds after the current time, you'd call this member like:
std::chrono::system_clock::now() + std::chrono::seconds(5)

The running thread should have obtained a lock on SharedCondition before calling this member, and should release the lock after this member has returned.

This member acts like wait_for(relative-time), returning std::cv_status::no_timeout if a notification arrived before absTime passed. Otherwise std::cv_status::timeout is returned.

The pseudo code for this member is identical to the pseudo code for the wait_for(relative-time) member above (except you gotta specify absolute time, bestie).

  • bool wait_until(std::chrono::time_point<Clock, Duration> const &absTime, Predicate pred) β€” implemented as a member template. Clock and Duration define identical types as mentioned at the previous member. Predicate is either a predicate function or a predicate function object (and it is not expecting arguments).

The running thread should have obtained a lock on SharedCondition before calling this member, and should release the lock after this member has returned.

As long as pred returns false, wait_until(absTime) gets called. If that function returns std::cv_status::timeout, then pred is called and its return value is what you get back. Otherwise true is returned.

The pseudo code for this member is identical to the pseudo code for the wait_until member above (just with pred also passed to wait_until, of course).


✨ STATIC MEMBER FUNCTIONS ✨

  • SharedCondition &attach(SharedMemory &shmem, std::ios::off_type offset = 0, std::ios::seekdir origin = std::ios::beg) β€” returns the SharedCondition object that interfaces to the shared condition variable sitting at offset (relative to origin) inside shmem.

An FBB::Exception gets thrown if the requested offset is invalid β€” meaning smaller than 0 or exceeding shmem.maxOffset(). 🚫

  • FBB::SharedCondition create(SharedMemory &shmem) β€” a shared condition variable gets initialized at the current offset of the SharedMemory object referred to by shmem, or at the first offset of the next physical shared data segment.

You get back a SharedCondition object interfacing to that freshly initialized shared condition variable. ✨

An FBB::Exception is thrown if there isn't enough memory available in the SharedMemory object to define a shared condition variable.

  • size_t size() const β€” returns the size, in bytes, of the shared condition variables stored in SharedMemory objects. πŸ“

πŸ’– EXAMPLE πŸ’–

okay bestie, this is the moment. here's a whole lil demo program that shows off everything SharedCondition can do β€” creating a shared memory segment with a condition attached, killing a segment, waiting, getting notified, the whole vibe. it's giving main character energy fr fr.

heads up: this is the exact code from the page, so copy it character for character and it'll work. don't go "fixing" the quotes, the curly ones are part of the deal πŸ’…

#include <iostream>

#include <bobcat/sharedcondition>
#include <bobcat/sharedmemory>

using namespace std;
using namespace FBB;

int main(int argc, char **argv)
try
{
    if (argc == 1)
    {
        cout <<
            "Argument:\n"
            "   c: create a shared memory segment + SharedCondition "
                                                    ", display ID\n"
            "   k <id>: kill shared memory segment <id>\n"
            "   m <id>: show a message every 5 secs, otherwise wait until\n"
            "           being notified in segment <id>\n"
            "   n <id>: notify the SharedCondition in segment ID <id>\n"
        ;
        return 0;
    }

    switch (argv[1][0])
    {
        case ’c’:
        {
            SharedMemory shmem(1, SharedMemory::kB);

            SharedCondition cond = SharedCondition::create(shmem);

            void *ptr = shmem.ptr();

            cout << "ID = " << shmem.id() << ", SharedCondition at " <<
                    cond.offset() << endl;
            break;
        }

        case ’k’:
        {
            SharedMemory shmem(stoll(argv[2]));
            shmem.kill();
            break;
        }

        case ’m’:
        {
            SharedMemory shmem(stoll(argv[2]));
            SharedCondition cond = SharedCondition::attach(shmem);

            cond.lock();
            cout << "Obtained the lock. Now waiting for a notification\n";

            while (true)
            {
                switch (cond.wait_for(chrono::seconds(5)))
                {
                    case cv_status::timeout:
                        cout << "Waited for 5 seconds\n\n";
                    break;

                    case cv_status::no_timeout:
                        cond.unlock();
                        cout << "Received the notification. Unlocked.\n";
                    return 0;
                }
            }
        }

        case ’w’:
        {
            SharedMemory shmem(stoll(argv[2]));
            SharedCondition cond = SharedCondition::attach(shmem);

            cond.lock();
            cout << "Obtained the lock. Now waiting for a notification\n";

            cond.wait();
            cout << "Received the notification. Unlocking.\n";

            cond.unlock();
            break;
        }

        case ’n’:
        {
            SharedMemory shmem(stoll(argv[2]));

            SharedCondition cond = SharedCondition::attach(shmem);

            cout << "Notifying the other after Enter ";
            cin.ignore(1000, ’\n’);

            cond.lock();
            cout << "Obtained the lock. Now notifying the other\n";
            cond.notify();
            cout << "Sent the notification. Now unlocking.\n";
            cond.unlock();
            break;
        }

    }
}
catch (exception const &exc)
{
    cout << "Exception: " << exc.what() << endl;
}

the tea β˜•: run it with no arguments and it prints its own lil menu (that's the argc == 1 branch, iconic honestly). then argv[1][0] decides which case you land in β€” c to create, k <id> to kill, m <id> to wait for 5-sec timeouts, w <id> to wait forever, n <id> to notify. and everything is wrapped in a try/catch so any exception gets printed instead of nuking your terminal. no cap, that's just good manners 🫢


πŸ“ FILES πŸ“

  • bobcat/sharedcondition β€” defines the class interface

πŸ‘€ SEE ALSO πŸ‘€

go read these too bestie, they're all part of the same friend group:


πŸ’€ BUGS πŸ’€

None Reported.

periodt. nothing to see here, we're clean ✨


πŸ¦‹ BOBCAT PROJECT FILES πŸ¦‹

Debian Bobcat project files:

  • libbobcat6 β€” debian package containing the shared library, changelog and copyright note;
  • libbobcat-dev β€” debian package containing the static library, headers, manual pages, and developer info;

🌈 BOBCAT 🌈

Bobcat is an acronym of `Brokken's Own Base Classes And Templates'.

like, the name is literally an acronym and that's so real of it πŸ’œ


This is free software, distributed under the terms of the GNU General Public License (GPL).


πŸŽ€ AUTHOR πŸŽ€

Frank B. Brokken (f.b.brokken@rug.nl).


and that's a wrap on the whole page, bestie πŸ’… this was part of libbobcat-dev version 6.07.01, with the copyright years running 2005-2025, and the page you just read is the FBB::Sh...ndition(3bobcat) manual. go forth and condition your shared memory, you absolute icon πŸŒΈπŸ¦‹βœ¨

πŸ“¦ where this page comes from