thread stuff

Discussion to talk about software related topics only.
Post Reply
ckoehler
Posts: 81
Joined: Sat Mar 13, 2010 9:04 pm

thread stuff

Post by ckoehler »

Hi,

A few question about the threads/tasks:

1. Do I need to set up critical section if I access the same data structure in two tasks, with one writing to it and one reading from it, without changing a thing?

2. How do I stop a task? I see OSTaskDelete, but it needs to be called from inside the task. I guess I can setup a semaphore and set it from some other task, and when it's set, the original task will quit?

3. What are some best practices re: 2. ?

4. What happens to objects allocated in a task? Are their destructors called?

Thanks!

Christoph
rnixon
Posts: 833
Joined: Thu Apr 24, 2008 3:59 pm

Re: thread stuff

Post by rnixon »

ckoehler wrote:Hi,

A few question about the threads/tasks:

1. Do I need to set up critical section if I access the same data structure in two tasks, with one writing to it and one reading from it, without changing a thing?

Yes, to avoid partial writes for data that cannot be written in a single operation.


2. How do I stop a task? I see OSTaskDelete, but it needs to be called from inside the task. I guess I can setup a semaphore and set it from some other task, and when it's set, the original task will quit?

Never call OSTaskDelete(). You always want to make sure you task can clean up and free any resources before it terminates. Use a flag in the while() loop of the task. When you set the flag to exit, the task can then free up resources or do whatever it needs to do to shut down gracefully, then it will exit the while() loop.

3. What are some best practices re: 2. ?

4. What happens to objects allocated in a task? Are their destructors called?

Allocated how? Letting the task shutdown and exit is safer if its a scope thing you are concerned about.

Thanks!

Christoph
ckoehler
Posts: 81
Joined: Sat Mar 13, 2010 9:04 pm

Re: thread stuff

Post by ckoehler »

1. Thanks, makes sense.

2. So when I exit the while(1) loop and there's nothing more to do, the task automatically exits? That's great, didn't know that!

3. The above explains this one, too.

4. And this one, I think. I just need to make sure to delete every memory object I allocated with new, etc, before the task quits, right?

Thanks!
User avatar
Chris Ruff
Posts: 222
Joined: Thu Apr 24, 2008 4:09 pm
Location: topsail island, nc
Contact:

Re: thread stuff

Post by Chris Ruff »

Beware..That doesn't fit my experience. returning from a task loop without OSTaskDelete(), I believe, is a bad thing and freezes the OS.
It has been a LONG time since I tried that, but that is my memory. Someone else here probably is doing this in some manner in their code.

I never exit task loops and never call OSTaskDelete(), but that is only my style.

Chris
Real Programmers don't comment their code. If it was hard to write, it should be hard to understand
ckoehler
Posts: 81
Joined: Sat Mar 13, 2010 9:04 pm

Re: thread stuff

Post by ckoehler »

I need to wait for some input, parse it, do some stuff until other input comes in. I was going to do that stuff in a task, and quit the task and start a new one (or idle) once other input comes in. A task may run for a long time.

How would you handle that scenario?
User avatar
Chris Ruff
Posts: 222
Joined: Thu Apr 24, 2008 4:09 pm
Location: topsail island, nc
Contact:

Re: thread stuff

Post by Chris Ruff »

I do what you are doing all of the time. The task waits on select(). the buffer that is read is handed to a function (say, buildParseItem()) whose task in life is to build discrete packets of parsable data. (This implies that you have a seperator between parsable blobs or some other way to know when you have a complete parsable buffer). buildParseItem() hands discrete parsable buffers to some function (say, parse()) that parses the buffer and performs actions based on the buffers contents, usually in a big switch.

Issues to handle are:

1. do the parsable blobs come in faster than the events in the switch can be performed?
2. is there a way to recognize a parsable blob?

If you can't keep up you will need to develop a FIFO that goes deep enough to support bursts of events. If you can't keep up *period* you have an overall system design snafu.

Chris
Real Programmers don't comment their code. If it was hard to write, it should be hard to understand
rnixon
Posts: 833
Joined: Thu Apr 24, 2008 3:59 pm

Re: thread stuff

Post by rnixon »

Chris has some good advice, and it is true that on other OS's returning from a task can be bad. But in the netburner OS, returning does clean up the task structure, and is a better method than delete because you do have an opportunity to clean up dynamic allocations, and the C++ objects go out of scope normally and their destructors are called.

I'm not sure I completely understand Chris's suggestion. Are you saying to leave a task active all the time, and it just blocks until there is data to parse? If so, then it sounds like a good way to me. If its something you use often, I don't see the down side to leaving the task active. Blocking isn't burning any cycles. Maybe use a semaphore to determine when its done.

Also, always check the return values from ostaskcreate(). That way you know if your stepping on another task priority.
Ridgeglider
Posts: 513
Joined: Sat Apr 26, 2008 7:14 am

Re: thread stuff

Post by Ridgeglider »

I agree: Chris' suggestions are the way to go.

First, I get nervous trying to kill tasks, particularly if the rationale is to clean stuff up that should probably be clean anyhow. I have not yet come across a good reason to make a task go totally away.

Second, if a task is blocking, say with select() or any of the other blocking calls (see pages 95-96 of the NNDK Programmers Manual at C:\nburn\docs\NetworkProgrammersGuide\NNDKProgMan.pdf it requires no resources until the event you are waiting for occurs. From my perspective there is little reason to try to kill a task.

For example if you are waiting for some input (say from a serial port) as Chris said, you will need to use select() to read from the desired port. One way I've found to reliably get appropriately formated chunks to parse (eg ones that are a complete 'sentence', is to take advantage of the fact that many devices that send data tend to blurt a sentence and then wait. This happens for example with most GPS devices, and with most NMEA devices as well as many others. If you clear a buffer, then setup select() so that it uses a short timeout (often 1-5 ticks), then select() will first load the buffer, then detect the timeout at the end of the blurt. This buffer can usually then be passed intact to a parser, acted upon. Just clear the buffer for the next go-round. Once you are done, your task blocks on the next select() call for the next blurt. You still need to handle the case that the blurt writes more data than you might allot in the buffer you are reading into, and you should probbaly write some code to stitch together or reject broken sentences that might occur if your device stops or starts at odd times.


Finally, Chris is once again correct in sayiing that if your task can't handle this throughput, you could try to buffer series of sentences by using a fifo, and then parsing them during some down-time. If there is no down time, you do have a design flaw.
ckoehler
Posts: 81
Joined: Sat Mar 13, 2010 9:04 pm

Re: thread stuff

Post by ckoehler »

Thanks for the advice guys! It seems like letting the task return and clean up is what I'm after. If I had one task that always sticks around I'd have to figure out how to update it with the new instructions, which seems more complicated than letting it expire and creating a new one.
Now if you guys are telling me that the overhead of creating a new task is much more expensive than updating an existing task with new instructions, I may reconsider.

In my use case, however, I figure that I will only need to do that a few times a day on average, bursting up to maybe a dozen times an hour; nothing time critical at all. There will not be any incoming serial communication, only a few outgoing commands. The user will trigger when a new task will start.

Now, if I were more familiar with the platform or had a different language, I would maintain a thread pool (of size 1 in this case) and a queue and populate the queue with stuff to do that the thread pool can work off. I am not sure I know how to do that here, and it also seems like the OS can handle letting the task expire just fine.

If I'm totally off, please let me know. I appreciate the advice, I always learn something new here!

Christoph
Ridgeglider
Posts: 513
Joined: Sat Apr 26, 2008 7:14 am

Re: thread stuff

Post by Ridgeglider »

Here are some suggestions:
Create a class that encapsulates a struct with some methods that allow the user to modify all the data reguired to be modified. Let one of the elements of the struct be an OS_Crit section in additon to the user-affected data. Have a 2nd object which is a semaphore. Each method called by the user uses the OS_Crit section of the struct to prevent access by other tasks until all the other data in the struct has been updated. When the user has modifed all the data in the struct, the user task POSTS the semaphore to the other task which is pending on that object. This is the signal that it is time for the 2nd task to act on the new data. When it does, the 2nd task also uses the struct's OS_Crit section to control acces while it either makes a local copy of the data, or to prevent the user from updating the original data while the 2nd task is acting on it (eg either reading or writing). This OS_Crit technique ensures that the non-atomic elements of the structs data remain intact as a set. See the NNDK Programmer's Manual example regarding circular buffers for further info on using OS_Crit sections.

Finally, below, I am copying from Larry's old post to the no-longer used Yahoo forum:
Hi,

The 4 OS objects you mentioned are more commonly referred to as OS
mutex objects. These OS calls are similar to semaphores with one key
difference. The task that gains control of the critical section, with
a call to OS_CritEnter, is the only task that can call OS_CritLeave.
If you have entered a critical section in a task and an interrupt
occurs, it is possible that a higher priority task will run after that
interrupt. If that task then hits an OS_CritEnter for the same object
then it will block and let the lower level task finish the critical
section. Since an interrupt is not truly running at any task
priority, it can not use these OS calls.
OSCriticalSectionObj is just a c++ class of this object. It will
automatically call OS_CritLeave when the scope for the section ends.

Semaphores are not as safe for protecting shared resources between
tasks since a post can be called from any task or interrupt.
Semaphores leave the possibility of your code accidentally triggering
an extra post without you being aware, especially if it is a highly
used semaphore. It is also possible to increase the semaphore count
with multiple posts so multiple non-blocking pends can occur.

USER_ENTER_CRITICAL is completely different from OS critical sections.
This is a MACRO call that has no direct interaction with the OS.
This MACRO will set the interrupt mask to 0x2700 which blocks all
maskable interrupts (levels 1-6). Since there are no interrupts
occurring, there will also be no task switching. This should be used
if you are sharing variables with an ISR to ensure they are not change
while you are accessing them in the task. While are in a
USER_ENTER_CRITICAL section, you should treat the section like a level
7 interrupt. No OS calls, no communication calls and as quick as
possible. If you called a post from one of these sections then the OS
will switch tasks but interrupts will remain blocked... your
application will not be happy. This will not cause you to miss
interrupts but rather delay them until you call USER_EXIT_CRITICAL.
If you are in the section for too long and the same interrupt occurs
twice, you will miss the first one. This is true for any ISR.

-Larry
Post Reply