Multithreading


IXFile components support threading and can be safely used in multithreaded environment; some conditions must be observed, however, to guarantee correct behaviour of objects. IXFile is designed to operate on one file and one thread at a time - that is single instance of the class can be accessed by only one thread. In other words the same object should not be shared between threads. Multiple instances of the class, however, can be safely accessed by multiple threads simultaneously for concurrent execution. Object contains necessary synchronization code to protect shared resources; for performance reasons, however, methods are not completely reentrant and allow concurrent execution on separate objects only. Object is not reusable in a strict sense but it does not cause any inconvience in development of multithreaded applications as explained futher. First, we must answer the question when multiple threads may need access to the same object. Perhaps the only situation is when multiple threads need access to the same file. But this can be - and should be - accomplished with multiple object accessing the same file. In other words each thread should create its own object, open the same file in appropriate sharing mode and access data. File locking should be probably used to protect data against corruption. Unbufferred transfer can be also useful to avoid accessing data processed by other threads. Take a look at 'MultiLock' sample source code to see how this can be done in practice. If you still must share the same object between threads, you can do this, but all operations on the object must be explicitly synchronized to ensure that every IXFile method is executed in critical section.

COM component is built with 'Both' threading model and therefore can be used in both single- (STA) and multithreaded (MTA) apartments. If it is used in STA, COM ensures that only one thread accesses the object at a time. Synchronization code is also called but it does not impair performance because methods contain only minimal required synchronization code. If object is created in MTA then it has all neccessary synchronization code and can be accessed by multiple threads. In both cases object is created in apartment that creates it, so no cross-apartment marshalling occurs.

Examples

C++

// error handling is omitted for clarity


//---------------
// Main function
//---------------

  int main(int, char**)
  {
   HANDLE th1, th2, th3;
   long ith1, ith2, ith3;
   long rth1, rth2, rth3;

// all threads have the same thread function
// but are started with different arguments

// start first thread

   th1 = CreateThread(NULL, 0, ThreadProc, (void *)1, 0, &ith1);   

// start second thread

   th2 = CreateThread(NULL, 0, ThreadProc, (void *)2, 0, &ith2);

// start third thread

   th3 = CreateThread(NULL, 0, ThreadProc, (void *)3, 0, &ith3);

   .
   . 
   .

// wait until all threads complete

   do
   {
    Sleep(1000);
    GetExitCodeThread(th1, &rth1);
    if(rth1 == STILL_ACTIVE)
     continue;
    GetExitCodeThread(th2, &rth2);
    if(rth2 == STILL_ACTIVE)
     continue;
    GetExitCodeThread(th1, &rth3);
    if(rth3 == STILL_ACTIVE)
     continue;
    break; 
   }

   .
   . 
   .

   return(0);
  }



//-----------------
// Thread function
//-----------------

  DWORD WINAPI ThreadProc(LPVOID mode)
  {
   IFile *ixf;
   long data;
   long lock;
   long rc;
 
// create IXFile object to be accessed by this thread only

   ixf = new IFile();

   ixf->SetLicenseKey("YOURLICENSEKEY");

// initialize object for reading/writing data
// and automatic file closing on object destruction

   ixf->Initialize(IXF_MODE_RDWR_CLOSE);

// open file for shared read/write because
// all objects operate on the same file
// OpenAdvanced() must be used because Open() would force exclusive access

   ixf->OpenAdvanced("test.bin", IXF_FILE_RDWR, IXF_FILE_SHARE_RDWR, IXF_FILE_OPEN);
 
// use unbuffered transfer to avoid accessing data of other threads
// unbuffered operations also automatically flush data buffer

   ixf->SetActiveBufferSize(0);
 
// process entire file

   do
   {

// lock one long integer at current file position
// to force exclusive access

    lock = ixf->LockLong(-1, 1, FALSE);

// read one long integer at current file position

    rc = ixf->GetLong(&data, 1);
    if(rc > 0)   // check for EOF
    {

// process data by simply increasing its value by "mode"

     data += (long)mode;

// write back modified data

     ixf->PutLong(&data, 1);

    }

// unlock file to allow other threads perform 
// operations on the same data
   
    ixf->Unlock(lock);
   
   } while(rc > 0);

// delete object and automatically close file

   delete ixf;

   .
   .
   .

  return(0);
 }

BASIC .NET

' error handling is omitted for clarity


'----------------
' Main procedure
'----------------

  Sub Main()

   Dim th1, th2, th3 As Threading.Thread

' each thread is started with intermediate procedure
' because there is no way to pass parameter to thread

   th1 = New Threading.Thread(AddressOf ThreadProc1)
   th2 = New Threading.Thread(AddressOf ThreadProc2)
   th3 = New Threading.Thread(AddressOf ThreadProc3)

   th1.Start()
   th3.Start()
   th2.Start()

' wait for threads to complete

   th1.Join()
   th2.Join()
   th3.Join()

   .
   . 
   .

  End Sub
 


'-------------------------
' Intermediate procedures
'-------------------------

  Sub ThreadProc1()
   ThreadProc(1)
  End Sub

  Sub ThreadProc2()
   ThreadProc(2)
  End Sub

  Sub ThreadProc3()
   ThreadProc(3)
  End Sub



'------------------
' Thread procedure
'------------------

  Sub ThreadProc(ByVal mode As Integer)

   Dim ixf As IXFLIB.IXFile
   Dim data as Integer
   Dim lock as Integer
   Dim rc as Integer
 
' create IXFile object to be accessed by this thread only

   ixf = New IXFLIB.IXFile

   ixf.SetLicenseKey("YOURLICENSEKEY")

' initialize object for reading/writing data

   ixf.Initialize(IXF_MODE_RDWR)

' open file for shared read/write 
' because all objects operate on the same file
' OpenAdvanced() must be used because Open() would force exclusive access

   ixf.OpenAdvanced("test.bin", IXFLIB.FileAccessMode.IXF_FILE_RDWR, IXFLIB.FileShareMode.IXF_FILE_SHARE_RDWR, IXFLIB.FileCreationDisposition.IXF_FILE_OPEN)
 
' use unbuffered transfer to avoid accessing data of other threads
' unbuffered operations also automatically flush data buffer

   ixf.SetActiveBufferSize(0)
 
' process entire file

   Do

' lock one long integer at current file position
' to force exclusive access

    lock = ixf.LockLong(-1, 1, False)

' read one long integer at current file position

    rc = ixf.GetLong(data, 1)
    If rc > 0   ' check for EOF

' process data by simply increasing its value by "mode"

     data += mode

' write back modified data

     ixf.PutLong(data, 1)

    End If

' unlock file to allow other threads perform 
' operations on the same data
   
    ixf.Unlock(lock)
   
   Loop While rc > 0

' close file manually; autoclosing option is not helpful in .NET 
' because objects are not deleted immediately

   ixf.Close()

   .
   .
   .

  End Sub