![]() |
![]() HP OpenVMS Systemsask the wizard |
![]() |
The Question is: Using DECthreads and C++ under OpenVMS Alpha 7.1, I need to set up inter-process communication. The OpenVMS Programming Concepts Manual says in section 2.2 that Global Sections provide the fastest way to do this. I have allocated a global page file section and used the memory to allocate hardware queue data structures (supported by the __PAL_INSQ... and __PAL_REMQ... built-ins in DEC C++). My application needs to perform various tasks on an on-going basis but proc ess messages as soon as possible, and here I have encountered difficulties. * I considered using an Event Flag from a Common Event Flag Cluster to indicate the transition from 0 messages to 1 message, and having the queue reader wait for that flag if it encounters an empty queue; but the DECthreads manual says in section B.11.8 t hat this will suspend the entire process, not just the thread that calls SYS$WAIT. * I could delay a short time and try again, but this kind of polling loop drains CPU time from the process's other threads, which have useful work to do. Given that I must cooperate with code that uses DECthreads, have I chosen the fastest tools available for inter-process communication? If so, how can I respond most quickly to the appearance of a message without suspending other threads? If not, what bett er combination of VMS Alpha tools would you recommend? The Answer is : Shared memory -- including group and system global sections, as well as galactic memory -- is the fastest available communications mechanism on OpenVMS. Shared memory does not provide event notification. Event notification can be implemented via AST, OpenVMS locks, event flags, $qio, mailboxes, $hiber/$wake, intracluster communications (ICC), DECnet, IP, and various other interprocess communications mechanisms available on OpenVMS systems. The usual approach is to monitor the status returned by the insertion, and to use a notification call when the message is inserted into an empty queue. The process retrieving messages from the queue continues to read messages from the queue until no more are available, then waits for a notification. (Many programmers will further include a periodic timer using $setimr or $schdwk, triggering a routine that causes the reader to revisit the queue, to avoid problems that could arise from errant (lost) notification processing.) As for notification in the DECthreads environment, if your application enables the kernel support for threads (i.e., if the main executable image is linked with /THREADS), then the best approach is to have the thread which is responsible for draining the queue wait in a call to $hiber when it finds the queue is empty. (The thread or process inserting messages would use $wake.) If the application does not enable the kernel support, then the consumer thread should block on a condition variable to avoid blocking the entire process. You can use an AST routine resulting from an interprocess request such as a mailbox I/O, an OpenVMS AST that is triggered when an OpenVMS lock is granted or is blocking another ("doorbell" locks), C signals, or similar -- make sure you use the appropriate DECthreads function for use at AST level. Certainly, using shared memory is the fastest communication mechanism. When working with shared memory, you will want to be familiar with issues of read-write ordering, as well as with the necessity for use of memory barriers. Shared memory has programming considerations around read-write ordering and memory barriers -- with C, you can be using keywords such as volatile (which instructs the compiler to avoid caching values in registers), as well as the C asm directives and PALcode memory barrier (__MB) calls. The interlocked primitives -- the interlocked queue and bitlock PALcode calls -- include memory barriers in the PALcode executed for the call. Regardless of whether or not a particular PALcode call includes a memory barrier, your application should explicitly include any necessary and appropriate memory barriers. The discussion of memory barriers assumes the application is now or will eventually operate on an OpenVMS symmetric multiprocessor (SMP) system. Any assumption of a non-SMP will fail if/when the application is moved to an SMP system -- the OpenVMS Wizard strongly recommends that the correct use of SMP-capable memory barriers be included. For instance, your producer must issue a "memory barrier" instruction after writing the data to shared memory and before inserting it on the queue; likewise, your consumer must issue a memory barrier instruction after removing an item from the queue and before reading from its memory. Otherwise, you risk seeing stale data, since, while the Alpha processor does provide coherent memory, it does not provide implicit ordering of reads and writes. (That is, the write of the producer's data might reach memory after the write of the queue, such that the consumer might read the new item from the queue but get the previous values from the item's memory. Of course, these read-write ordering and memory barrier issues do not occur in communications between threads in the same process, so long as the threads use mutexes to protect the shared data. Like PALcode calls, DECthreads mutexes provide memory barriers implicitly. Note that DECthreads does not provide any mutexes that can synchronize across multiple processes on OpenVMS -- the DECthreads mutex support operates only among threads within a single OpenVMS process. Of course, these read-write ordering and memory barrier issues do not occur in communications between threads in the same process -- processes where kernel-threads are not enabled -- as long as they use mutexes to protect the shared data, since the mutexes provide the memory barriers implicitly; however, mutexes are not available to synchronize memory access across threads across processors in an OpenVMS SMP configuration. Of the available interprocess synchronization mechanisms -- examples include common event flags, $hiber/$wake, mailbox I/O, the distributed OpenVMS Lock Manager -- $hiber/$wake is among the cheapest. However, as you may be aware, using $hiber in a multithreaded process without the kernel support enabled can be unreliable and will block all threads from executing during the time that the calling thread is scheduled to "run". Thus, in that situation, it is important to block the thread on a condition variable -- otherwise, the particular communications and synchronization mechanism chosen can be irrelevent, as they will all have nearly the same performance impact. The difficulty is, how do you signal the condition variable when an item is placed on the queue? Since there is no way to do this from outside the process, the immediate solution is to provide a way to do it from inside the process, via an AST. The two obvious possibilities are a completion AST from an asynchronous $qio call such as a mailbox read, and a "doorbell" AST set up by using the blocking AST support in the $enqw call. Either of these mechanisms can be triggered by the producer when it inserts an item on the queue and the status indicates that the queue was previously empty (i.e., there is no need to expend the cost of the system service if the queue was not previously empty, since the first insertion would have already done so). The AST routine should call the appropriate DECthreads function to signal the condition variable (note that the usual signal function is not supported from an AST and you must use one with the "_int" suffix in its name) and then re-arm the notification mechansim (i.e., by calling $qio or $enqw again). For additional information on memory barriers, shared memory requirements, and read-write ordering, please see the Alpha Architecture Reference Manual. The reference is available for download via pointers in the FAQ. Also please see existing discussions of shared memory and related topics, including (4487), (4051), (3791), (3635), (3365), (2486), (2637), (2181), (860), and others.
|