Zach Wegner wrote:bob wrote:Or, use processes correctly.
I used to use threads but they have some serious compatibility issues across a wide variety of platforms, whereas in unix, fork() works the same way every time.
You just have to learn how to use global memory. I use the AT&T SYSV stuff (shmget, shmat, etc) and it works on every unix platform I have run on, unlike threads where there is always an issue here and there to deal with.
The problem you had is one that is well-known, and you just need experience to not fall into that. It took me a couple of hours to remove posix thread support and change over to processes. I did this a few years back when using an 8-way AMD box that was misbehaving in the posix threads library somewhere.
Fortunately, on unix, the memory footprint doesn't change because of the copy-on-write way things get duplicated after a fork() call. Data initialized before the fork, and not modified after the fork, gets shared among all processes just as it would with threads. Ditto for the executable code... So there is no real benefit to doing it either way, except that processes is probably safer for the beginner because you only share what you explicitly share. With threads, _everything_ is shared, which leads to lots of unexpected bugs.
Yeah, it is an "in front of the monitor" error, but it can get irritating. Also note that these are the only two problems I've had, unless you count trying to get shared memory to work in OS X and scrapping it for mmap().
I think Allessandro is right in that it's a problem either way, processes or threads. Whenever I've used threads in the past though, it seemed easier to keep track of the variables that are going to need to be thread-safe. There are many issues with shared memory that are a bit harder to deal with, like, do I make history shared? If not, how do I clear it? I'd rather not send a message to each process telling it to clear it. If I do share it, I also have a variable history_counter that keeps track of the highest value in the table. Now I need to put that in a shared struct, or put a pointer to it, and that makes my single processor code messier.
Also, I've heard you mention portability problems both with pthreads and mmap(). What are they?
Oh, and Edsel, those bugs are now long gone. I just have to find all the other ones!
The portability issues I have found are:
1. difficult to determine "CPU time" reliably. Some systems report it to you on a "per thread" basis, others report cumulative time. With processes you can always get per process usage.
2. how many physical processors do you use when you start 4 threads? Linux gives 4. Sun's Solaris uses 1 unless you use _another_ threads library function to tell it to use one physical processor per thread. Other systems break when you use this call.
3. Even starting threads has some quirks in how the lone argument is passed, what can be passed, and some systems crash if you are not precise in what you do, and what you do can be different for some systems.
4. If you start and terminate threads, you can run into difficulties when the underlying thread library doesn't really terminate threads but you thought they were wiped out. So the next thread creation uses an old thread with uninitialized local memory and another zap happens.
5. Perhaps the biggest problem for new parallel programmers is that with threads, _everything_ is shared. Even "local memory". Which means any thread can blow out another thread with a simple programming bug, which can make debugging difficult.
6. Threads share everything, including file descriptors and such, which can complicate debugging if you try to write debugging output to a file because there is too much of it going to the screen.
Using processes, the main point becomes carefully thinking about what is shared. Things that you initialize before the fork() don't need to be shared unless they later get modified. If this happens, they must be shared or each process will continue to use the old/original value, not the new one. But now, nobody can bother anybody else's local memory. So the "bugs" become inverted in a way. With threads you share too much, with processes you can share too little. Either way it won't work correctly.
I would not claim one approach is any better or worse than the other, except that I run on so many different platforms, threads were problematic enough that I decided to do away with them. Threads are simpler from a programming standpoint, but then they offer more opportunities for subtle bugs since _everything_ is visible in all threads. The main advantage I have seen for processes is that I have not had any case where they have not worked on a unix platform, whereas with threads I had problems here and there. Remember that I started out with threads, but after running on several different platforms for WCCC-type events, I decided to use something that I think is a bit more complicated (processes) over something that was easier to use but more problematic over a wide range of platforms.
It also greatly cut down my tech support type email queries.