Single-threaded design philosophy
NxCore API was designed to be single-threaded, and
internally uses thread-specific (thread local storage, or TLS) data structures
to allow complete isolation between multiple threads running the API.
Each of your multiple NxCore processing threads is completely isolated from
others.
Each API call you make works on the data within your thread.
All API calls are deterministic and synchronous.
Parallel processing
The single-threaded design allows you to have a large
number of independent threads processing different tapes at the same time, with
zero synchronization requirements.
Just start a thread, have that thread call ProcessTape(), and that thread will
process that tape without you worrying about any further
threading/synchronization issues.
Use of global variables
The only global variable that's safe to use in multiple
NxCore threads is NxCoreClass.
While you can use __declspec(thread) for your global variables, a better way is
to create a struct that will contain all your global data, and pass a pointer
to that structure in the UserData field of ProcessTape().
Look at the example below -- the PerThreadData structure is used to simulate
global variables.
Lifetime of NxCore data structures
All NxCore data structures are allocated at beginning of
ProcessTape, and destroyed before ProcessTape returns.
NxString values remain constant, but virtually all other structures get
overwritten on every callback.
If you're looking to use your own synchronization mechanisms, you'll need to
manage copying the data off yourself.
Splitting of a tape across multiple threads
A single tape or stream can be processed on multiple threads. The playback is not syncronized between threads. Using ExcludeOpraExch allows for filtering of the playback.
Here's an example of a very simple, thread-safe, multiple-tape message counter
#define _CRT_SECURE_NO_WARNINGS #include <windows.h> #include <stdio.h> #include <process.h> #include "NxCoreAPI.h" #include "NxCoreAPI_class.h" static NxCoreClass nxCoreClass; struct PerThreadData { __int64 counter; int rc; int time; const char* tape; PerThreadData() : counter(0), rc(0), time(0), tape(0) {} }; static int __stdcall nxCoreCallback(const NxCoreSystem* pNxCoreSys, const NxCoreMessage* pNxCoreMessage) { PerThreadData *perThreadData = (PerThreadData*) pNxCoreSys->UserData; perThreadData->counter++; if ((pNxCoreMessage->MessageType==NxMSG_STATUS)&&(pNxCoreSys->ClockUpdateInterval >= NxCLOCK_MINUTE)) { printf("Tape Date: %02d/%02d/%d Tape Time: %02d:%02d:%02d\n", pNxCoreSys->nxDate.Month,pNxCoreSys->nxDate.Day,pNxCoreSys->nxDate.Year, pNxCoreSys->nxTime.Hour,pNxCoreSys->nxTime.Minute,pNxCoreSys->nxTime.Second); } return NxCALLBACKRETURN_CONTINUE; } static void handleTapeThread(void *arg) { PerThreadData *perThreadData = (PerThreadData*) arg; long startTick = GetTickCount(); perThreadData->rc = nxCoreClass.ProcessTape(perThreadData->tape, 0, 0, (int) perThreadData, nxCoreCallback); perThreadData->time = GetTickCount() - startTick; } int main(int argc, char** argv) { if (argc == 1) { fprintf(stderr, "%s tape1 [tape2 tape3 ...]\n", argv[0]); return -1; } if (!nxCoreClass.LoadNxCore("NxCoreAPI.dll") && !nxCoreClass.LoadNxCore("C:\\Program Files\\Nanex\\NxCoreAPI\\NxCoreAPI.dll")) { fprintf(stderr, "Can't find NxCoreAPI.dll\n"); return -1; } const int numTapes = argc - 1; HANDLE *threads = new HANDLE[numTapes]; PerThreadData *perThreadData = new PerThreadData[numTapes]; for (int i = 0; i < numTapes; i++) { perThreadData[i].tape = argv[i + 1]; printf("Start Tape: %s\n",perThreadData[i].tape); threads[i] = (HANDLE) _beginthread(handleTapeThread, 0, (void*) &perThreadData[i]); } WaitForMultipleObjects(numTapes, threads, TRUE, INFINITE); for (int i = 0; i < numTapes; i++) { const PerThreadData& ptd = perThreadData[i]; printf("Tape[%s] Count[%I64d] RC[%ld] Millis[%ld]\n", ptd.tape, ptd.counter, ptd.rc, ptd.time); } delete threads; delete perThreadData; return 0; }