Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Multithreading and a two-stage lock -- is there a better way?

36 views
Skip to first unread message

Frederick Virchanza Gotham

unread,
Feb 1, 2023, 5:07:45 AM2/1/23
to
I have one thread that is reading and writing to a COM port, so it pretty much looks like this:

void Thread_Entry_Point(....)
{
for (;;)
{
Comms.Read( . . . );
ProcessPacket( . . . );
Comms.Write( . . . );
}
}

I have several worker threads which at any time can manually insert a packet into the COM port, and so the COM port thread looks more like this:

void Thread_Entry_Point(....)
{
string str;

for (;;)
{
str = Get_Manual_Packet();

if ( str.empty() ) Comms.Read( . . . );
else Comms.SetReadBuf(str.cbegin(), str.cend());

ProcessPacket( . . . );
Comms.Write( . . . );
}
}

I've spent the last two days figuring out deadlock and trying to put together a viable multithreaded locking system to make this possible. I have devised a two-stage lock to work as follows:

(Step 1) A worker thread, in order to manually insert a packet, must acquire both the primary lock and the secondary lock. When the worker thread is finished putting the packet into a global buffer, it then releases the secondary lock.
(Step 2) The COM port thread, when it calls "Get_Manual_Packet", tries to acquire the secondary lock. Once it has acquired the secondary lock, it processes the packet and sends it out the COM port, and reads back the reply.
(Step 3) Once the COM port thread has received the reply, it releases the secondary lock, and also releases the primary lock.
(Step 4) With both the primary and secondary lock now free, another worker thread can acquire both of them and manually insert another packet.

What makes this a little bit complicated is:
Complication 1) In Step 1, the worker thread acquiring the primary lock might be the most recent thread to have acquired the primary lock, and so it could try to double-lock a mutex (by the way I'm not using recursive mutexes at all).
Complication 2) In Step 3, it is the COM port thread that releases the primary lock even though it was a worker thread that acquired the lock.

In order to get around both Complication 1 and Complication 2, I have changed the primary lock from an "std::mutex" to a "std::atomic_flag". Instead of making a spin lock, I wait on the 'atomic_flag' to change.

Here's the code I've written so far. Please tell me if you see potential for deadlock or any other kind of multithreading debacle. By the way I realise there's a very minor race condition in the setting of "id_owner1" but that's only in Debug mode. The Comms thread calls the methods "lock_weak, unlock_weak", and the worker threads call "lock_strong, unlock_strong". The primary lock is 'device1' and the secondary lock is 'device2'.

#ifndef HEADER_INCLUSION_GUARD_TWO_STAGE_MUTEX_HPP
#define HEADER_INCLUSION_GUARD_TWO_STAGE_MUTEX_HPP

#include <cassert> // assert
#include <atomic> // atomic_flag, ATOMIC_FLAG_INIT
#include <mutex> // mutex, lock_guard

#ifdef NDEBUG
/* Nothing */
#else
# include <cstdlib> // abort
# include <thread> // thread::id, this_thread::get_id
# include <iostream> // cerr
#endif

class TwoStageMutex {

std::atomic_flag device1 = ATOMIC_FLAG_INIT;
std::mutex device2;

void acquire_device1(void) noexcept
{
debug << "Trying to acquire device1\n";
for (;;)
{
if ( false == device1.test_and_set() )
{
SetOwner1();
debug << "device1 acquired\n";
return;
}

device1.wait(true);
}
}

void release_device1(void) noexcept
{
ClearOwner1();
device1.clear();
device1.notify_all();
debug << "device1 released\n";
}

void acquire_device2(void) noexcept(false)
{
debug << "Trying to acquire device2\n";
device2.lock();
SetOwner2();
debug << "device2 acquired\n";
}

void release_device2(void) noexcept
{
ClearOwner2();
device2.unlock();
debug << "device2 released\n";
}

public:

TwoStageMutex(void) noexcept {}

void lock_weak(void) noexcept(false) // Only invoked by RS232 thread
{
// The next line prevents double-lock by the same thread
assert( id_owner2 != std::this_thread::get_id() );
acquire_device2();
}

void lock_strong(void) noexcept(false) // Invoked by one of several worker threads
{
acquire_device1();
lock_weak();
}

void unlock_strong(void) noexcept // Invoked by one of several worker threads
{
// The next two lines make sure that this thread is the one that locked them
assert( id_owner1 == std::this_thread::get_id() );
assert( id_owner2 == std::this_thread::get_id() );

release_device2();
}

void unlock_weak(void) noexcept // Only invoked by RS232 thread
{
// The next line makes sure that the current thread is the one that locked it
assert( id_owner2 == std::this_thread::get_id() );
// The next two line make sure that device1 is currently locked but by another thread
assert( id_owner1 != std::thread::id() ); // compares with 'no such thread'
assert( id_owner1 != std::this_thread::get_id() );

release_device2();
release_device1();
}

#ifdef NDEBUG
struct StructForCerr {
StructForCerr &operator<<(char const*) noexcept { return *this; }
} debug;
void SetOwner1 (void) noexcept {}
void SetOwner2 (void) noexcept {}
void ClearOwner1(void) noexcept {}
void ClearOwner2(void) noexcept {}
#else
struct StructForCerr {
std::mutex mtx;
StructForCerr &operator<<(char const *const p) noexcept
{
try
{
std::lock_guard<std::mutex> mylock(mtx);
std::cerr << " - - - Thread " << std::this_thread::get_id() << ": " << p;
}
catch(...) { std::abort(); }
return *this;
}
} debug;

std::atomic<std::thread::id> id_owner1{}, id_owner2{};

void SetOwner1 (void) noexcept { id_owner1 = std::this_thread::get_id(); }
void SetOwner2 (void) noexcept { id_owner2 = std::this_thread::get_id(); }
void ClearOwner1(void) noexcept { id_owner1 = std::thread::id(); }
void ClearOwner2(void) noexcept { id_owner2 = std::thread::id(); }
#endif

//TwoStageMutex(void) = delete;
TwoStageMutex(TwoStageMutex const &) = delete;
TwoStageMutex(TwoStageMutex &&) = delete;
TwoStageMutex &operator=(TwoStageMutex const &) = delete;
TwoStageMutex &operator=(TwoStageMutex &&) = delete;
TwoStageMutex *operator&(void) = delete;
TwoStageMutex const *operator&(void) const = delete;
template<typename T> void operator,(T&&) = delete;
};

class StrongGuardForTwoStageMutex {
TwoStageMutex &tsm;
public:
StrongGuardForTwoStageMutex(TwoStageMutex &arg_tsm) noexcept(false) : tsm(arg_tsm)
{
tsm.lock_strong();
}

~StrongGuardForTwoStageMutex(void) noexcept
{
tsm.unlock_strong();
}

StrongGuardForTwoStageMutex(void) = delete;
StrongGuardForTwoStageMutex(StrongGuardForTwoStageMutex const &) = delete;
StrongGuardForTwoStageMutex(StrongGuardForTwoStageMutex &&) = delete;
StrongGuardForTwoStageMutex &operator=(StrongGuardForTwoStageMutex const &) = delete;
StrongGuardForTwoStageMutex &operator=(StrongGuardForTwoStageMutex &&) = delete;
StrongGuardForTwoStageMutex *operator&(void) = delete;
StrongGuardForTwoStageMutex const *operator&(void) const = delete;
template<typename T> void operator,(T&&) = delete;
};

#endif

Chris M. Thomasson

unread,
Feb 1, 2023, 8:47:24 PM2/1/23
to
On 2/1/2023 2:07 AM, Frederick Virchanza Gotham wrote:
> I have one thread that is reading and writing to a COM port, so it pretty much looks like this:
[...]

This can be tricky to get right. Locking order, ect... I wonder, humm...
Perhaps the following address based locking might work for your setup.

https://groups.google.com/g/comp.lang.c++/c/sV4WC_cBb9Q/m/Ti8LFyH4CgAJ

?

It helps wrt being able to lock multiple objects at once without any
fear of deadlock.

Scott Lurndal

unread,
Feb 2, 2023, 11:55:07 AM2/2/23
to
Forty years ago we (Burroughs) developed a hardware mechanism to
lock multiple objects without fear of deadlock.

A lock was a 20-digit field in memory. The first two digits were the
hardware lock to control access to the remaining digits. The hardware
would use tests-and-set on that digit to acquire the lock atomically.

The lock structure format is as follows:

^ Information ^ Digits ^
| Lock status field | 00-01 |
| Lock owner field | 02-05 |
| Lock waiter link field | 06-09 |
| Lock number field | 10-13 |
| Lock number link field | 14-17 |
| Reserved | 18-19 |

The owner field contained the four digit task number of the
task that owned the lock, once it is acquired. The
waiter link field stores the task number of the first task
in a linked list of tasks waiting for the lock.

The lock number field contained a "canonical lock number" which
was an unique four digit value that was used for deadlock prevention
(see below).

The Lock number link field contained the value of the canonical
lock number of the most recent previously obtained but not released
lock.

Each task structure in the MCP (OS) has a four digit field
that contains the current canonical lock number for the most
recently obtained lock.

The LOK instruction will compare the lock number field in the
lock structure to the current canonical lock number in the
task structure, and if it is less than or equal will fail the
instruction. This prevents A->B, B->A style deadlocks and
prevents lock loops (a second attempt to acquire a lock with
the same or lower canonical lock number will fail).

If the LOK instruction detects that the lock is currently held
by another task, it will interrupt into a small microkernel to
reschedule the task, otherwise it will mark the lock acquired
and move the current lock number from the task entry to the
lock number link field and store the new lock number into the
task entry.

===== Lock/Unlock (LOK)/OP=60 =====

==== Format ====

^ OP ^ AF ^ BF ^ A Syllable ^

''OP = 60''

**AF** Unused and reserved; can be specified as an indirect field length. A literal flag causes an invalid instruction fault (IEX = 21).\\
**BF** Instruction variant; can be indirect.

^ Variant ^ Function ^
| 10 | Event cause no interrupt |
| 09 | Event cause and reset |
| 08 | Event reset and wait |
| 07 | Test happened status |
| 06 | Event reset |
| 05 | Event wait |
| 04 | Event cause |
| 02 | Conditional lock |
| 01 | Unconditional lock |
| 00 | Unlock |

All other BF values cause an invalid instruction fault (IEX = 26).

**A** is the address of the lock/event structure. Address can be indexed, indirect, or extended. The final address controller must be UN, or an invalid instruction fault (IEX = 03) occurs. The final address must be Mod-2, but the hardware does not check it.\\
If BF has a value of 00-02, A represents a lock structure.\\
If BF has a value of 04-10, A represents an event structure.

The lock structure format is as follows:

^ Information ^ Digits ^
| Lock status field | 00-01 |
| Lock owner field | 02-05 |
| Lock waiter link field | 06-09 |
| Lock number field | 10-13 |
| Lock number link field | 14-17 |
| Reserved | 18-19 |

^ Note: The lowest memory address is 00.^

The event structure format is as follows:

^ Information ^ Digits ^
| Event status field | 00-01 |
| Event owner field | 02-05 |
| Event waiter link field | 06-09 |
| Event designator field | 10-13 |
| Event count | 14-19 |

^ Note: The lowest memory address is 00. ^

If any of the lock/event structure values contain undigits, an invalid
instruction (IEX=07) occurs.

^ Note: This instruction only executes with the privileged enable set otherwise an invalid instruction fault (IEX=02) is reported. ^

==== Function ====

LOK examines the lock/event structure (A) and, depending on its value
and the instruction variant (BF), modifies the lock/event structure
(A) and associated lock fields in the [[processor_state:reinstate_list|reinstate list]] entry for the
current task, and other tasks owning or contending for the lock or
event.

The lock waiter link field and the event waiter link field provide the
MCP kernel with the task number, which execution is linked to, for a
specific change of the current lock or event structure.

The lock number field and the lock number link field implement a
canonical lock algorithm to prevent deadlock situations. All locks are
assigned a level number; a task that owns a lock at level
n can only get a lock at level n+1 or greater. A
lock at a level lower than n can be acquired only by a task that
has released all locks at or above that level in reverse order from which
it had acquired them.

The processor must determine if a lock is owned or available. If the
owner field of the lock structure is zero, the lock is available. If the
owner field is not zero, the lock is owned.

The processor must determine if an event has happened. If the event owner
field is zero, the event has happened. If the event state field is not
equal to zero, the event has not happened.

The event designator field ensures that the current structure is an event
structure. A nonzero value in this field causes a fault.

^ Note: The equivalent field in a lock structure (lock number) is always a nonzero value. ^

The event count field identifies a particular occurrence of a particular
event since the same structure can be used for multiple purposes over a
period of time.

The machine-dependent lock status field of the lock/even structure (A)
can also represent the status of the structure, with one value
representing owned, and another representing available. The lock status
field of the lock structure is not used to determine if the lock is
available.

==== Variants ====

The variants are described as follows:

=== BF=00 Unlock ===

This variant releases a Lock and, if any task is waiting for this lock, causes
an interrupt to the MCP kernel.

Obtain exclusive rights to the lock structure specified by the A address.

The value of the lock owner field must equal the current task
number; otherwise, relinquish exclusive access rights
to the lock structure, cause an invalid instruction fault (IEX=06)
and terminate the instruction with no futher action.

Compare the lock number field of the lock structure (A)
with the MCP canonical lock number field located in the
[[processor_state:reinstate_list|reinstate list entry]] for the current task. If they are not
equal, relinquish exclusive access rights to the lock
structure; cause an invalid instruction fault (IEX=06) and
terminate the instruction with no further action.

Otherwise, store zeros into the lock owner field of the
lock structure (A) to indicate that this lock is available.
It also stores the contents of the lock number link field of
the lock structure (A) into the MCP canonical lock number
field that is located in the reinstate list entry for the
current task.

Examine the lock waiter link field of the lock structure. If it is equal to
zero, set the [[processor_state:comparison_flags|Comparison flags]] to **EQUAL**, relinquish exclusive access rights to
the lock structure and terminate the instruction with no further action.

If the lock waiter link field is not equal to zero, perform the following
operations:
- Set the comparison flags to HIGH.
- Assemble and save, within the processor, the released contended lock value for future update of the instruction interrupt cause descriptor in the [[processor_state:kernel_data_area|kernel data area]].
- Assemble and save, within the processor, the pointer to the reinstate list entry of the task specified by the task number in the lock waiter link field of the lock structure (A). The pointer is to be used for update to the instruction interrupt cause extension descriptor (C7AAAAAA).
- Assemble and save, within the processor, the next instruction address. The next instruction address will be used to update the interrupt frame in the reinstate list entry for the current task.
- Relinquish exclusive access rights to the lock structure.
- Perform an interrupt procedure that reports an instruction interrupt in the interrupt descriptor in the [[processor_state:kernel_data_area|kernel data area]].

=== BF=01 Unconditional Lock ===

This variant competes for the lock specified by the Lock Structure (A) and, if
the lock is owned, causes an interrupt to the MCP kernel.

Obtain exclusive rights to the lock structure specified by the A address.

Compare the lock number field of the lock structure (A) with the MCP canonical lock number field located in the reinstate list entry for the current task. If the lock number field is less than or equal to the MCP canonical lock number field, relinquish exclusive access rights to the lock structure, raise an invalid instruction fault (IEX=06) and terminate the instruction.

If the lock is available, perform the following operations:
- Store the active task number in the lock owner field.
- Copy the contents of the MCP canonical lock number field, that is located in the active reinstate list entry, into the lock number link field of the lock structure.
- Copy the contents of the lock number field of the lock structure into the MCP canonical lock number field that is located in the active reinstate list entry.
- Set the comparison flags to **EQUAL**.
- Relinquish exclusive access rights to the lock structure and terminate the instruction.

If the lock is owned, perfrom the following operations:
- Copy the lock owner field of the lock structure (A) into the task number owning field located in the reinstate list entry for the current task.
- Copy the lock waiter link field of the lock structure (A) to the next task in list field located in the reinstate list entry for the current task.
- Store the current task number into the lock waiter link field of the lock structure (A).
- Store the waiting lock value into the state indicator field located in the reinstate list entry for the current task.
- Set the comparison flags to LOW.
- Assemble and save, within the processor, the failed lock value for update of the instruction interrupt cause descriptor in the kernel data area.
- Assemble and save, within the processor the pointer to the reinstate list entry of the task specified by the task number in the lock owner field of the lock structure (A). The pointer is to be used for update to the instruction interrupt cause extension descriptor (C7AAAAAA).
- Assemble and save, within the processor, the next instruction address to point to the instruction following the current instruction for future update of the interrupt frame in the reinstate list entry for the current task.
- Relinquish exclusive access rights to the lock structure.
- Perform an interrupt procedure that reports an instruction interrupt in the interrupt descriptor in the kernel data area.

Ignore the trace enable toggle, even if trace is enabled. Do not perform a trace hardware call following this variant.

=== BF=02 Conditional Lock ===

This variant attempts to obtain the lock specified by the lock structure at the location specified by the A-address.

Obtain exclusive rights to the lock structure specified by the A address.

Compare the lock number field of the lock structure (A) with the MCP canonical lock number field located in the reinstate list entry for the current task.

If the lock number field is less than or equal to the MCP canonical lock number field, relinquish the exclusive access rights to the lock structure, cuase an instruction fault (IEX=06), and terminate the instruction.

If the lock is available, perform the lock available case of the unconditional lock variant, BF=01.

If the lock is owned, perform the following operations:
* If the lock owner field is equal to the task number, set the comparison flags to LOW, relinquish exclusive access rights to the lock structure, and terminate the instruction.
* If the lock owner field does not equal the current task number, set the comparison flags to HIGH, relinquish exclusive access rights to the lock structure, and terminate the instruction.

=== BF=04 Event Cause ===

This variant causes an event and signals all tasks awaiting the event that the event is available.

Obtain exclusive rights to the lock structure specified by the A address.

If the event designator field is not equal to zero,
relinquish exclusive rights to the event structure, cause an
invalid instruction fault (IEX=06), and terminate the instruction.

If the event is in the happened state, increment the event
count field by one, or if the field is already at the maximum
counter value, reset the counter to zero. Then set the
comparison flags to EQUAL, relinquish the exclusive access rights
to the event structure, and terminate the instruction.

If the event is in the not happened state, increment the event
count field of the event structure (A) by one, or if the field is
already at the maximum counter value, reset the counter to zero.
Then set the event owner field to the happened state (zero).

Examine the event waiter link field of the event structure (A). If it is
equal to zero, set the comparision flags to EQUAL, relinquish exclusive
access rights to the event structure and terminate the instruction with no
further action.

If the event waiter link field is not equal to zero, perform the following
operations:
- Set the comparison flags to HIGH.
- Store zeros in the event waiter link field of the event structure.
- Assemble and save the released event value within the processor for future update of the instruction interrupt cause descriptor in the kernel data area.
- Assemble and save, within the processor, the pointer to the reinstate list entry of the task specified by the task number in the lock owner field of the lock structure (A). The pointer is to be used for update to the instruction interrupt cause extension descriptor (C7AAAAAA).
- Assemble and save, within the processor, the next instruction address to point to the instruction following the current instruction for future update of the interrupt frame in the reinstate list entry for the current task.
- Relinquish exclusive access rights to the event structure.
- Perform an interrupt procedure that reports an instruction interrupt in the interrupt descriptor in the kernel data are

=== BF=05 Event Wait ===

This variant causes the current task to wait until the specified event, if it is in the not happened state, is caused.

Obtain exclusive rights to the lock structure specified by the A address.

If the event designator field is not equal to zero, relinquish exclusive rights to the event structure, cause an invalid instruction fault (IEX=06), and terminate the instruction.

If the event is in the happened state, set the comparison flags to EQUAL, relinquish exclusive rights to the event structure, and terminate the instruction.

If the event is in the not happened state, perform the following operations:
- Copy the event owner field of the event structure (A) into the task number owning field located in the reinstate list entry for the current task.
- Copy the event waiter link field of the event structure (A) into the next task in list field located in the reinstate list entry for the current task.
- Store the current task number into the event waiter link field of the event structure (A).
- Store the waiting event value into the state indicator field located in the reinstate list entry for the current task.
- Set the comparison flags to LOW.
- Assemble and save, within the processor, the failed event value for future update of the instruction interrupt cause descriptor in the kernel data area.
- Assemble and save a 6-digit zero field for future update of the instruction interrupt cause extension descriptor (format 7000000).
- Assemble and save, within the processor, the next instruction address. The next instruction address will be used to update the interrupt frame in the reinstate list entry for the current task.
- Relinquish exclusive access rights to the event structure.
- Perform an interrupt procedure that reports an instruction interrupt in the interrupt descriptor in the kernel area.

Ignore the trace enable toggle, even if trace is enabled. Do not perform a trace hardware call following this variant.

=== BF=06 Event Reset ===

This variant resets the happened state of the event to the not happened state.

Obtain exclusive rights to the lock structure specified by the A address.

If the event designator field is not equal to zero,
relinquishe exclusive rights to the event structure, cause an
invalid instruction fault (IEX=06), and terminate the instruction.

If the event is in the not happened state, set the comparison
flags to HIGH, relinquish exclusive rights to the event
structure, and terminate the instruction.

If the event is in the happened state, store the current task
number into the event owner field of the event structure (A) and
store zeroes into the event waiter link field of the event
structure (A).

Set the comparison flags to EQUAL, relinquish exclusive
rights to the event structure, and terminate the instruction with no further
action..

=== BF=07 Event Test Happened Status ===

This variant tests whether or not an event happened.

Obtain exclusive rights to the lock structure specified by the A address.

If the event designator field is not equal to zero,
relinquish exclusive rights to the event structure, cause an
invalid instruction fault (IEX=06), and terminate the instruction.

If the event is in the not happened state, set the comparison
flags to HIGH, relinquish exclusive rights to the event
structure, and terminate the instruction.

If the event is in the happened state, set the comparison
flags to HIGH, relinquish exclusive rights to the event
structure, and terminate the instruction.

=== BF=08 Event Reset and Wait ===

This variant resets an event structure to the not happened state
and forces the current task to wait until the event has been
caused.

Obtain exclusive rights to the lock structure specified by the A address.

If the event designator field is not equal to zero, LOK
relinquishes exclusive rights to the event structure, causes an
invalid instruction fault (IEX=06), and terminates the instruction.

If the event designator field is equal to zero, LOK does the
following:
- Store the current task number into the event owner field of the event structure (A) and into the task owning field in the reinstated list entry for the current task.
- Copy the event waiter link field of the event structure (A) into the next task in list field located in the reinstate list entry for the current task.
- Store the waiting event value into the state indicator field located in the reinstate list entry for the current task.
- Set the comparison flags to LOW.
- Assemble and save, within the processor, the failed event value for future update of the instruction interrupt cause descriptor in the kernel data area.
- Assemble and save a 6-digit zero field for future update of the instruction interrupt cause extension descriptor (format C7000000).
- Assemble and save, within the processor, the next instruction address to point to the instruction following the current instruction for future update of the interrupt frame in the reinstate list entry for the current task.
- Relinquish the exclusive access rights to the event structure.
- Perform an interrupt procedure that reports an instruction interrupt in the interrupt descriptor in the kernel area.

Ignore the trace enable toggle, even if trace is enabled. Do not perform a trace hardware call following this variant.

=== BF=09 Event Cause and Reset ===

This variant causes an event that allows any task, waiting for the
event, to continue processing and then leaves the event structure
in the not happened state.

Obtain exclusive rights to the lock structure specified by the A address.

If the event designator field is not equal to zero,
relinquish exclusive rights to the event structure, cause an
invalid instruction fault (IEX=06), and terminate the instruction.

If the event designator field is equal to zero, increment the
event count field of the event structure (A) by one, or if the
field is already at the maximum counter value, reset the counter
to zero. Then store the current task number into the event
owner field of the event structure (A).

Examine the event waiter link field of the event structure (A).

If it is equal to zero, set the comparison flags to EQUAL,
relinquish exclusive rights to the event structure, and
terminate the instruction.

If it is not equal to zero, perform the following operations:
- Set the comparison flags to HIGH.
- Store zeros in the event waiter link field of the event structure.
- Assemble and save the released event value within the processor for future update of the instruction interrupt cause descriptor in the kernel data area.
- Assemble and save a 6-digit zero field for future update of the instruction interrupt cause extension descriptor (format C7000000).
- Assemble and save, within the processor, the next instruction address to point to the instruction following the current instruction for future update of the interrupt frame in the reinstate list entry for the current task.
- Relinquish exclusive access rights to the event structure.
- Perform an interrupt procedure that reports an instruction interrupt in the interrupt descriptor in the kernel data area.

=== BF=10 Event Cause No Interrupt ===

This variant causes an event and reports to the current task the
head of the list of the tasks that are waiting for this event.

Obtain exclusive rights to the lock structure specified by the A address.

If the event designator field is not equal to zero,
relinquish exclusive rights to the event structure, set the
comparison flags to LOW, and terminate the instruction.

If the event is in the happened state, increment the event
count field by one, or if the field is already at the maximum
counter value, reset the counter to zero. Then set the
comparison flags to EQUAL, relinquish the exclusive access rights
to the event structure, and terminate the instruction.

If the event is in the not happened state, increment the event
count field of the event structure, A, by one, or if the field is
already at the maximum counter value, reset the counter to zero.
Set the event owner field to the happened state (zero).

Examine the event waiter link field of the event structure (A).

If it is equal to zero, set the comparison flags to EQUAL, relinquish exclusive access rights to the event structure, and terminate the instruction.

If it is not equal to zero, perform the following operations:
- Use the event waiter link field of the event structure as an index into the reinstate list to locate a new specific reinstate list entry. The address of the specified reinstate list entry is stored in memory in the IX1 field, relative to base 0, for the current task (format C7AAAAAA).
- Set the event waiter link field of the event structure (A) to zero.
- Set the comparison flags to HIGH, relinquish exclusive access rights to the event structure and terminate the instruction with no further action.

==== Comparison Flags ====

The [[processor_state:comparison_flags|Comparison Flags]] are set according to
the variant.
0 new messages