LabWindows/CVI

cancel
Showing results for 
Search instead for 
Did you mean: 

LabWindows / CVI , thread safe variable async timer

Hallo community,

 

I recently came across multi theadening and ways to handle threads in a way to avoid race conditions.

 

I did read this:  https://www.ni.com/en/support/documentation/supplemental/06/multithreading-in-labwindows--cvi.html

 

But I am not sure if I undestood everything correctly.

 

What I have is:

 

- Win11 PC

- global CONSTANTS

- global variables

- main - function:

- async timer function

- some sub functions, in the terms of   int myfunc(void);

 

What I want to achive:

- access different hardware and record the time it takes to retrieve data and write it to a file

 

My impression is, that I build a software that runs into race condition issues.

 

e.g.: the timer event is executed, when it shouldn't anymore

 

 

From the documentation above I see that a MainFunction and a ThreadFunction can be made save against race conditions using a thread safe scalar.

 

My question is:

 

Can I simply exchange :

 

       int CVICALLBACK ThreadFunction (void *functionData)

 

by my asynctimer callback function, e.g.:

 

       int CVICALLBACK asynctimerCallback (int reserved, int timerId, int event, void *callbackData, int eventData1, int eventData2)

 

attached you may find my example code.

 

Thank you in advance,

Philipp

 

 

 

 

 

 

 

 

 

 

0 Kudos
Message 1 of 7
(339 Views)


@der_Phil  ha scritto:

My question is:

 

Can I simply exchange :

 

       int CVICALLBACK ThreadFunction (void *functionData)

 

by my asynctimer callback function, e.g.:

 

       int CVICALLBACK asynctimerCallback (int reserved, int timerId, int event, void *callbackData, int eventData1, int eventData2)

The short answer is "no": asynchronous timers and threads built upon functions found in the utility library are different beasts, even if they share the common concept of multithreading.

To use as async timer you have to do nothing about the thread, which is completely handled by OS and library functions. On the other hand, functions in the utility library give you a more flexible and powerful instrument at the cost of a higher complexity in the code.

Both of them, however, share the need for interlock and synchronization among processes in the code, since spawned threads run independent from the main thread (which normally handles the UI) and other threads.

 

CVI offers several instruments to protect you from unwanted conditions, which are documented in the page you linked. The code you posted is evidently only a proof-of-concept and gives no hints about race conditions or concurrent access to variables or objects in the system: go on in your development and when you'll have an actual doubt we could try helping you.

 

Before going on you may benefit studying the examples that ship with CVI both on async timers and on multithreading.

 

*** Edit: you cannot use an async timer function as the function installed by CmtScheduleThreadPoolFunction (), if this is what you are really asking for ***



Proud to use LW/CVI from 3.1 on.

My contributions to the Developer Community
________________________________________
If I have helped you, why not giving me a kudos?
0 Kudos
Message 2 of 7
(294 Views)

Hallo Roberto,

 

thank you for your answer.

 

I assumed that "no" is the short answer, but it is nice to get this confirmed.

 

To this comment:

[...]

The code you posted is evidently only a proof-of-concept and gives no hints about race conditions or concurrent access to variables or objects in the system

[...]

 

Well, I experience the following:

 

If I set the variable  timerStep   small enough   (e.g.: timerStep = 0.001)

 

I see that the EVENT_TIMER_TICK is executed even if the timer should not be active anymore.

 

der_Phil_0-1758546954297.png

 

If I understand my code correct, this should not be possible.

 

My thoughts are going in two directions:

 

1st:    Some kind of race condition, between main()-thread and async timer thread.

 

This is because:

- while loop in main() accesses global variable actualCount

- actualCount is changed in async timer callback

 

 

2nd:  SetAsyncTimerAttribute (timerId, ASYNC_ATTR_ENABLED, 0);

 

Mabye this takes to long?

 

Thank you,

Philipp

 

 

 

 

0 Kudos
Message 3 of 7
(253 Views)

Hello Philipp,

indeed I see the same behavior as you, and consider that my machine is significantly slower than yours since I see times no lower than 3 msec while you see 1.

 

First of all I tried discarding the timer instead of disabling it, with no change.

 

Next I focused on func_WriteData () function where opening and closing the file on every execution clearly is a bottleneck: I modified your code a little in order to maintain the file open while the thread executes and noted a significant increase in steps executed (50 to 70 steps instead of 10) and no step executed past timer disable. Given this, I tend to consider that the async timer code is critical in your example since it performs only disk I/O and adding a*slow* operation like opening and closing the file every time seems to result in the last execution queued and executed after the timer "should be" disabled (it actually  gets disabled after the last iteration concludes, I suppose).

I do not know how all this may apply to your actual situation, but these are significant ideas in my opinion.

 

I attach the modified code for you to revise it.



Proud to use LW/CVI from 3.1 on.

My contributions to the Developer Community
________________________________________
If I have helped you, why not giving me a kudos?
Message 4 of 7
(231 Views)

Hello Roberto,

 

yes, constantly opening/closing a file is surely not efficient.

So thank you for your input, really appreciate it.

 

However I still get following screen:

der_Phil_0-1758620049606.png

 

I also tend to not trust the time statements to much, since "elapsed time" occurs to be 0.0000 once in a while.

 

Also , if I set:  timerStep = 0.005   I get results like this:

der_Phil_1-1758620237759.png

 

Although the overall behavior is more like expected, the elapsed time values should rather stay constant in the region of 0.005[s] now, shouldn't they?

 

I do agree...the critical part seems to be the async timer.

More precise: I guess it is the WriteData()-function, which is called by the timer.

If I comment out the "writing" part the code runs smooth as expected.

 

So I guess writing to a file takes more than 1ms ?

As the example code was intended to be a quick test I implemented the two for-loops.

As this topic seems to evolve I tend to rather first write to a local buffer and than use fwrite() to write all data in one step to a file.

Would you agree or would you suggest a different approach for fast data recording?

 

 

But still I wonder if using a global variable in  main()   and   async timer   can lead to a race condition and a thread lock should be implemented.

 

 

Thank you,

Philipp

 

 

 

 

 

 

0 Kudos
Message 5 of 7
(209 Views)

Well, going a bit further, I stripped out the output on screen in the async timer and passed 'et' to func_WriteData (), getting stable, coherent value for intervals 1 to 7 msec (tested with 100 loops): it appears that screen output is also a bottleneck and messes up things. (Hint: 'et' should be moved to a local variable in the timer callback, there is no need to have it global)

 

Regarding writing to disk it depends on how long your app is intended to runt and how many data you have to handle: short-term apps with a few data like in the example give no problem, you could accumulate results in a memory array and write them at the end of the process. For long-term running apps and/or huge amount of data you could try using thread safe queues; create the queue and write to it in the spawned thread, and install the queue reader in another thread / the main thread to move slow operations away from the timer thread in order to keep it the shortest possible.

In this case you'll have to properly design this producer/consumer architecture: you could for example write to the queue on each iteration and read from it when say 10 elements are present and write them to file in a single pass to reduce I/O operations, in order to obtain a stable and sustainable mechanism.



Proud to use LW/CVI from 3.1 on.

My contributions to the Developer Community
________________________________________
If I have helped you, why not giving me a kudos?
Message 6 of 7
(201 Views)

Thank you Roberto.

 

the application run-time can differ indeed from project to project.

The time range can shift between hours-per-day to non-stop-for-months.

 

Thanks again for your input.

Philipp

 

0 Kudos
Message 7 of 7
(170 Views)