Please enable JavaScript to view this site.

DAQFactory User's Guide

Navigation: 16 Serial and Ethernet Communications

16.7 Low Level Comm Synchronization

Scroll Prev Top Next More

There are many times when you may want to communicate on a single comm port from multiple threads.  For example, with a user protocol you may have several different I/O types that you might want to trigger at different intervals.  If the protocol is a poll request, it is possible for a two requests to be sent out the serial port from different threads before the device can respond.  This can generate problems.  To solve this problem we use something called synchronization.  To help with this, comm devices have two functions, Lock() and Unlock():

Using Lock() and Unlock():

A synchronization flag (usually called a Mutex, or Critical Section) controls access to particular parts of code.  Only one thread can have access to any of the protected code segments at one time.  If you place all your low level comm calls inside protected code sections, then you can control the order they get executed and prevent overlap.  In a polling protocol this usually means you put a Write() and a Read() inside the same code segment.  To mark the beginning of a code segment, you should use the Lock() function of the device.  This function returns 1 if it is safe to execute the subsequent code, or 0 if it is not.  The function will return 0 if another thread is using the port and your code can't acquire the lock within the port's current timeout period.  You should never proceed if it returns 0 and should probably generate an error message instead:

 

Private strDataIn

if (!Device.MyDevice.Lock())

   throw("Port in use!")

endif

Device.MyDevice.Write("GiveMeData"+Chr(13)

strDataIn = ReadUntil(13)

Device.MyDevice.Unlock()

To mark then end of the protected segment, you should use the Unlock() function.  This function always succeeds.  Its actually relatively straight forward if you follow this format, and not nearly as dangerous as normal Mutexes.  There are several points though:

1) If an error occurs, Unlock() never gets called, leaving the port blocked.  This can be solved with a try / catch block:

 

Private strDataIn

if (!Device.MyDevice.Lock())

  throw("Port in use!")

  return

endif

try

  Device.MyDevice.Write("GiveMeData"+Chr(13)

  strDataIn = ReadUntil(13)

  Device.MyDevice.Unlock()

catch()

  Device.MyDevice.Unlock()

  throw()

endcatch

2) You could inadvertently stop the sequence midstream and therefore leave the device locked and the port blocked.  Fortunately, unlike Mutexes, you can call Unlock() on a device from any sequence and it will release the port.  This should be used as a last resort though, and proper try/catch coding is the best solution.  

3) The Lock() and Unlock() are port specific, not device specific.  This allows you to apply multiple protocols to the same serial port without interference.  

For advanced users experienced with Mutexes:

The synchronization used here is a combination of a mutex and a flag.  The flag indicates whether the synchronization object is signaled.  The mutex and the flag are specific to each port.  The flag itself is protected by a mutex.  

The big difference between this method and a standard mutex is that the DAQFactory method allows you to reset the synchronization object from any thread.  It also does not have the requirement that for each Lock() there must be an equal number of Unlocks.  Instead, a single Unlock() call will fully release the sync object.  There are good reasons why mutexes are not normally done this way, but DAQFactory is different from pretty much all other languages in that the user can start and stop threads at will and therefore could easily lockout their synchronization object and must have a way to reset it from another thread.