Kuro5hin.org: technology and culture, from the trenches
create account | help/FAQ | contact | links | search | IRC | site news
[ Everything | Diaries | Technology | Science | Culture | Politics | Media | News | Internet | Op-Ed | Fiction | Meta | MLP ]
We need your support: buy an ad | premium membership

[P]
Threads Considered Harmful

By moshez in Technology
Mon Nov 18, 2002 at 10:23:07 AM EST
Tags: Software (all tags)
Software

A little known anecdote is that after Dijkstra's famous paper in "Communications of the ACM" titled "Goto Considered Harmful", and after the reactions, the ACM has adopted a policy of not allowing papers with "... Considered Harmful" in the title. It seems it decided such papers are inherently inciting. Thus, Goto has a position of sole infamy in the history of the ACM. Nonetheless, if ever a feature has been easy to compare to Goto in its destructiveness, threads would be that feature. Threads are, in a sense, "the goto that keeps on giving": once you have spawned a thread, there is no way to know anymore which line is being executed in parallel with yours -- at least not with a lot of careful locking that a moment's neglectfulness proves useless. When deciding to spawn a thread, you are conciously giving up any and all protection your language gives you from making stupid mistakes.

The rest of the paper will try to convince you that while there is a limited class of problems for which threads are a good solution, your problem is almost certainly not among them -- no matter what your problem is.


The Parable of the Programmer

Frequently beginning programmers will try to write network programs as multithreaded programs. Granted, this is tempting -- all the convenience of being in one process on one hand, and all the convenience of writing natural, blocking code.

An even bigger problems with this approach is that it does not blow up directly in its naive user's face. Indeed, some speculate that when the CPU sees this code, while executing, it silently chuckles to itself. For it is a feature of threaded code that, like a malevolent application of Murphy's law, its bugs only tend to be manifested under heavy load. The chief problem with threads are race conditions and dead locks. Both tend to occur randomly, depending on the OS scheduling whims. Thus, a bug is usually more likely to be manifested when there are many threads.

Let us consider an example. Assume that the code has a bug: if two threads try to execute line 524 simulataneously, a bug will occur (perhaps underestimating needed memory for some buffer). Now, line 524 is really simple code: threads will only spend 0.01% of their time there. If there are two threads, it is a one-to-ten-thousand shot that they will be there at the same time. Thus, this code might test with two, or even three threads for hours at end without a problem. Even if there is a buffer overflow, it might go unnoticed for a while. However, if a sudden surge of activity suddenly occurs, it might cause a hundred threads to be launched. Because of the probablistic phenomenon known as the "Birthday Paradox", it is suddenly a one-in-two shot that two threads will execute line 524 at the same time. Now, the memory very quickly becomes out of sync with the needed buffer and the program crashes.

Imagine yourself in the condition of the poor programmer who forgot to put a lock around line 524. The bug he is getting is nowhere near line 524: all he knows is that "when the activity surges, within a few minutes, the program crashes".

"Hah!" you say, laughing at the poor slob, "I'll just put a lock". Indeed, locks are wonderful things. If the programmer put a lock around line 524, none of this would have happened, right? When a thread tried to get to line 524, it would stop, waiting for a lock. The next thread would leave line 524 with the program in a consistent state, and all would be well in the world. Except, now there's another problem. Actually, we also needs to use that same lock at line 420, because it changes the same variable, but this is easy! We just put the locks wherever we need to. Now, in fact there are two objects needing a lock at line 524: it also modifies an object which line 367 modifies. But protecting a line with two locks is just as easy as protecting it with one lock, right? And in fact, line 509 also needs those two locks. So around line 524, you acquire two locks and release two locks, and the same around line 509. Since these lines are in different files, you forgot that to keep the order of lock acquiring the same. Now, lock acquisition is a very quick operation, so the chances of your thread being interrupted between one and the other are negligible, so once again the program happily runs.

Until there is another surge of activity.

With a thousand threads waving around, it seems one was interrupted between the two lock acquisitions. And control was passed to another thread. Which tries to acquire the locks at a different order. Now, one thread has lock A and wants lock B. One thread has lock B and wants lock A. Neither of them will do anything until they have the lock they want. So, lock A and lock B are forever unavailable. Now, at some point or other, every thread wants one or the other so all threads gets stuck like a crowd watching a gruesome car accident. Since the application spawns more threads when none are available to handle requests, more and more threads are spawned, and get stuck. At some point, the computer collapses from the load.

The poor programmer tries to reproduce the condition time after another. Sadly, the fluke which happened does not seem to happen again. So he writes the bug off as "freak accident".

Our programmer is nothing if not persistent. Come hell or high waters, he will solve the bugs. Finally, the accumulation of bug reports convinces him that there is a problem. He manages to reproduce it, and by a freak accident -- a real one, this time -- his debugger manages to find where the threads are stuck. In a stroke of genius, the programmer sees where he goes wrong. However, it is not trivial to always remember the correct order, is it? "Aha!" thinks our programmer. The solution is clear: add one global lock around all lock acquisitions. Indeed, this solves his problem...until he finds performance has gone down the drain.

All the threads are constantly waiting on the global lock. In essence, the program is now serialized -- there is little benefit from the multiple threads. Unfortunately, the locks are still being locked and released like a terrorist in the Palestinian authority, costing performance.

Alternatives

  • Multiple processes -- if you have relatively few concurrent processes and have little need for communication between them, this is a good solution. There are many variants on the solutions here, and the most common optimization is preforking.
  • Event-based programming
  • Co-operative threads, such as co-routines.

Multiple Processes

This solution is very popular, and the Apache server is probably its poster boy. Other well known examples are the popular mail servers like sendmail, exim, postfix or qmail.

All of these have in common that each handler is relatively independent of the others. They communicate mainly through the file system, and use coarse locks around the few file-system-modifying operations.

Event-Based Programming

This solution is both highly efficient and very safe -- but it does require code restructuring. Luckily, this kind of code restructuring is a useful skill to learn, since most GUI toolkits (BeAPI excepted) are event-based.

Event-based programming requires that your code will be structured as handlers for events, each of them finishing quickly. A main loop calls handlers as events occur. In some sense, the main loop acts as a global lock. However, an event handler can be sure no other code is executing. This means all the bugs caused by concurrency, explained above, go away.

Co-operative Threads

While this solution requires the most support from the language, it is often a good solution. Here, a thread knows that no matter what, it will not be pre-empted. So it can safely perform operations which will leave the program in a temporarily inconsistent state, and only relinquish control when it is safe to do so. Naturally, great care must be given to relinquish control often enough so one thread does not block many services.

Conclusion

Doing more than one thing at a time, or even believably simulating it, is inherently difficult to program, and is easy to make bugs. The only hope of the programmer is that the bugs will be obvious to find and easy to fix. Threads let the programmer forget about the core problem often enough that when the bugs are made they will only be found under heavy load and will require state-of-the-art algorithms to solve (see the Dining Philosophers and Drinking Philosophers).

Other Resources

Sponsors

Voxel dot net
o Managed Hosting
o VoxCAST Content Delivery
o Raw Infrastructure

Login

Poll
Are threads harmful?
o Yes 8%
o Yes 12%
o Hell yes! 39%
o I haven't read the article 40%

Votes: 74
Results | Other Polls

Related Links
o Apache
o Why Threads are a Bad Idea
o Tom Christiansen's Post to Perl's user list
o Also by moshez


Display: Sort:
Threads Considered Harmful | 451 comments (442 topical, 9 editorial, 0 hidden)
Hmmm... (4.33 / 6) (#1)
by epepke on Mon Nov 18, 2002 at 02:17:56 AM EST

About a year ago, there was an Op-Ed piece in Communications of the ACM about O-O programming called "Hello World Considered Harmful." So, I don't think that such a policy as you describe exists.


The truth may be out there, but lies are inside your head.--Terry Pratchett


The Policy May Have Been Overruled (5.00 / 3) (#2)
by moshez on Mon Nov 18, 2002 at 02:23:19 AM EST

But apart from such frivolous examples, I don't know of any other examples. The ACM did have such a policy, at least for a short while after Dijkstra's paper.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
OK (4.50 / 2) (#4)
by epepke on Mon Nov 18, 2002 at 02:26:19 AM EST

But the "Hello World Considered Harmful" was actually quite good and I don't think frivolous at all. It suggested a very simple program with an object and a sayHello member function to get people thinking about O-O from day 1.


The truth may be out there, but lies are inside your head.--Terry Pratchett


[ Parent ]
I Must Say, This Reminds Me (5.00 / 2) (#6)
by moshez on Mon Nov 18, 2002 at 02:30:12 AM EST

Of the libhello project

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Yeah, that's cute (n/t) (none / 0) (#13)
by epepke on Mon Nov 18, 2002 at 03:00:54 AM EST


The truth may be out there, but lies are inside your head.--Terry Pratchett


[ Parent ]
Thanks :) (n/t) (none / 0) (#17)
by moshez on Mon Nov 18, 2002 at 03:16:33 AM EST



[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Unfortunately.... (3.33 / 3) (#30)
by washort on Mon Nov 18, 2002 at 04:13:19 AM EST

it was done in Java.

[ Parent ]
OTOH, (4.62 / 8) (#3)
by _Quinn on Mon Nov 18, 2002 at 02:25:36 AM EST

with proper abstraction of shared data, many of your locking problems go away.  (Want to fiddle with shared data?  Ask the singleton, which does its own locking.)

The rule of thumb is to consider threading as a speed optimization, not a way to simplify the programming task.  Just remember M.A. Jackson's rules about optimization:

"

  1.  Don't do it.
  2.  (for experts only)  Don't do it yet.
"

- _Quinn
Reality Maintenance Group, Silver City Construction Co., Ltd.

A critical missing part of the rule of thumb. (4.50 / 8) (#5)
by kuran42 on Mon Nov 18, 2002 at 02:29:59 AM EST

It is not a runtime speed optimization if you do not have more than one CPU. It sounds obvious; people forget it anyway.

With regard to "proper abstraction", this was addressed quite well in the article I thought. By the time you have proper abstraction, you've killed performance. In some specific instances where you are only sharing a small amount of information, this might not apply - but if you are only sharing a small amount of information, I would ask why you are using a shared memory space at all.

--
kuran42? genius? Nary a difference betwixt the two. -- Defect
[ Parent ]

not true (2.83 / 6) (#27)
by suntzu on Mon Nov 18, 2002 at 04:04:49 AM EST

I'll provide an example from a program I had to write for a class.

Let's say I want to run a program that will finger a list of users on a unix system. I have a function that fingers a given user name, and enters data into a table based on what finger returns. The vast, vast majority of the run-time of this function is spent idling, waiting for a reply over the network. If the function is run sequentially over a long list of usernames, the program takes a loooooong time to run.

A good way around this problem is to have the finger function be it's own thread. Everytime the finger function is called on a username, it's spawned in a new thread. While waiting for network replies, it can sleep, leaving free processor cycles for threads that happen to be doing something. When the reply is in, it obtains a lock on the table that the data is entered into, enters the data, and quietly terminates after giving the lock up. While it was sleeping, hundreds of other threads quickly performed what few processor hungry actions they had, like entering data into the table.

The program runs much, much faster if it's multithreaded. This is obvious even when running it over a list of names as short as 50 or 100 users. Time that would be wasted on the processor cycling idly, waiting for the network to reply, is instead used to run things that take advantage of the processor. This is true even on a single processor system, though it's obviously even more useful on a multi-processor system.

But yeah, this sort of thing isn't that common. Still, it's a good tool to have around. Multi-threading that only improves speed on multi-processor systems only arises in programs where each thread takes full advantage of the CPU, all the time.



[ Parent ]
did you read the article? (3.40 / 5) (#29)
by washort on Mon Nov 18, 2002 at 04:10:43 AM EST

This is specifically the approach that is being warned against here. Thread-per-socket is far from the only way to do concurrent network I/O and is definitely the least robust or efficient. Please read the article again, especially the bit on "event-based programming".

[ Parent ]
ahem (none / 0) (#215)
by suntzu on Mon Nov 18, 2002 at 07:08:18 PM EST

My comment was not one on the article, but a reply to the parent comment. Yes, i read the parent comment. Yes, I realize that event based programming would be a reasonable, and likely, better solution.

Did I claim otherwise? No. I refuted a claim that said that multithreading only speeds up applications on multiprocessor systems. I pointed out a case where multithreading speeded up a program on a single processor system. Hence, the parent poster's claim, as the subject line said, was "not true."

That said, I think an event based program would be cleaner, maybe more efficient. The program I had to write was implemented in C. I'm by no means a Java apologist, but if I had my choice, I'd say Java's style of event handling would be a better choice that semaphores and threads in C. I never claimed that my way was the best, just that it was a counter-example to someone else's claim.

It's amazing, that in four replies to my comment, all missed the point. I wasn't ripping on the article, or promoting threads. Just pointing out a mistaken claim.



[ Parent ]
Twisted (4.00 / 3) (#37)
by moshez on Mon Nov 18, 2002 at 04:34:14 AM EST

It just so happens I was the person who implemented the finger server for Twisted, an event-based networking framework. Let me tell you, the most time it took me (out of the 15-20 minutes) was to read the (fairly ugly) RFC. Thinking about the events took no time at all as did thinking about the concurrency (because there was no concurrencry :)

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
so... (none / 0) (#217)
by suntzu on Mon Nov 18, 2002 at 07:10:31 PM EST

What does that have to do with my comment? I was replying to the poster who said a speed increase is only gained through multiprocessor systems. That is not true. I made no other claims (about implementation time, efficiency compared to event-handling based approaches, or otherwise).

[ Parent ]
So What You're Saying Is... (none / 0) (#301)
by moshez on Tue Nov 19, 2002 at 07:07:25 AM EST

Threads are better than using nothing of: threads, multiple processes, event handling. How is that, in any way, relate to anything anyone here talked about?

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
once again (none / 0) (#309)
by suntzu on Tue Nov 19, 2002 at 08:36:13 AM EST

yes, but dammit, my comment was not intended to be a statement on how good threads were. i just pointed out that it is possible to get a performance boost using threads on a single processor system. that is all. nothing more, nothing less.

i wasn't making a general comment on the article, but replying to the specific claim that threads only give a performance boost on multi-processor systems. my reply was related to it's parent comment, and really nothing else. it's related tangentially to your article, because it relates directly to a comment which related directly to your article. that's what a tangent is. a topic related indirectly to the main topic.

it's quite simple really. this is a threaded discussion. if it were flat, and all replies were directly to your story, you'd have a valid point. but one poster commented about a problem with threads (that they only boost performance on multi-processor systems). that was related to the article, because it gave yet another piece of support to your idea that threads are generally not necessary. i gave a counter-example to the multi-processor thing. i honestly don't see why this is seen as promoting threads, or being irrelevant to anything posted by anyone. it's directly related to at least one comment, which is itself related directly to the story.

was my comment that difficult to not read into? i mean, i feel like my explanations are getting repetitive, because i'm not even refuting your claims. your claim (threads are generally bad) and my claim (threads can, in some special cases, give a performance boost on a single processor system) are not mutually exclusive by any stretch of the imagination. they can coexist peacefully. they are not opposed to one another. i've stated a benefit of threads. you've argued that the downside is greater than the benefit. where am i disagreeing with you?



[ Parent ]
You're totally misguided. (4.00 / 4) (#54)
by radeex on Mon Nov 18, 2002 at 05:03:05 AM EST

  1. Threading is not the only way to solve the "don't wait for I/O to happen" problem. Have you ever head of "Non-blocking I/O"?
  2. This discussion-thread was about CPU efficiency. Your response has nothing to do with that. In fact, in terms of CPU, threading is often a slower way to deal with I/O than simply using non-blocking sockets.
The program runs much, much faster if it's multithreaded.

It doesn't "run much faster" so much as waste less time blocking on I/O.
--
I DEMAND RECOMPENSE!
[ Parent ]

in a threaded message board (2.00 / 2) (#219)
by suntzu on Mon Nov 18, 2002 at 07:15:01 PM EST

a reply to a parent comment need not be strictly about the article.

my reply was not. it was a counter example (nothing more, nothing less) to the claim that threading only gives a performance boost in a multi processor system.

I reread my comment 3 or 4 times. It in no way agrees with either of your points, and in fact, I agree with both.

Your last sentence though... well, it does run faster. It terminates before the nonmultithreaded version would. Another method (possibley event based programming) may very well terminate faster than the multithreaded version. I made no claims to the contrary. Read my post again.



[ Parent ]
What - saving time doesn't save time?? (none / 0) (#313)
by greenrd on Tue Nov 19, 2002 at 08:41:49 AM EST

It doesn't "run much faster" so much as waste less time blocking on I/O.

No, it really does run much faster.


"Capitalism is the absurd belief that the worst of men, for the worst of reasons, will somehow work for the benefit of us all." -- John Maynard Keynes
[ Parent ]

I don't know... (3.00 / 3) (#129)
by tzanger on Mon Nov 18, 2002 at 11:10:47 AM EST

A good way around this problem is to have the finger function be it's own thread. Everytime the finger function is called on a username, it's spawned in a new thread. While waiting for network replies, it can sleep, leaving free processor cycles for threads that happen to be doing something. When the reply is in, it obtains a lock on the table that the data is entered into, enters the data, and quietly terminates after giving the lock up. While it was sleeping, hundreds of other threads quickly performed what few processor hungry actions they had, like entering data into the table.

I don't know about you, but I'd have done that in an event loop. In fact, that is exactly how I did it for a control system. Each possible command was a subclass of a "Task" object. Each subtask had a "take" function which gets called when a packet comes in. So I can do things like this:

    TGetInput *t;
    for(int i=0; i<32; i++) {
    t = new TGetInput();
    t->inputno = i+1;
    Controller->Send(t);
    }
This can happen whenever it wants, and operates independent of my polling loop:
    while(!done) {
    TGetStatus *t = new TGetStatus();
    Controller->Send(t);
    }
The Controller object has a rxchar callback and builds up a response packet from characters received. When enough characters are received and it makes sense, it creates a packet out of the data and rattles through its list of waiting tasks (tasks waiting for a response), calling each one with a pointer to the packet. If the waiting tasks sees that the packet is for it, it does whatever it needs and returns true, which causes the controller code to delete the waiting task and the packet. It's slick as hell, and saves me a LOT of work.



[ Parent ]
thanks... (none / 0) (#221)
by suntzu on Mon Nov 18, 2002 at 07:20:22 PM EST

At least you didn't jump all over me for no good reason. You provided a tangential counter point, with some code. Cool.

I must say, I'm amazed that four seperate posters said the same thing, and all thoroughly seemed to miss the point of my comment. I wasn't defending threads. Simply providing a counter example to the parent comment's statement.

One more thing. I was required to implement the program in C. This is not too far fetched in the real world either. So, I'm wondering, is there an easy way to do non-threaded event based programming in C? I'm honestly asking; I don't know, and I'm curious.



[ Parent ]
Events (none / 0) (#363)
by Znork on Wed Nov 20, 2002 at 06:42:25 AM EST

As you've might have seen in other comments, you can use select() for this. You can fire off all the queries at once (or several at a time if you want to conserve resources) and then use an event loop with select() followed by nonblocking read() to multiplex the incoming data. That is the traditional way to solve this kind of problem without resorting to threads.

[ Parent ]
Databases ? (5.00 / 2) (#36)
by bugmaster on Mon Nov 18, 2002 at 04:33:38 AM EST

What about SQL databases ? They usually maintain a complicated data structure on disk, as well as an extensive memory cache. However, each DB user expects his transactions to work as if he were alone on the system.

How do you program a database without sharing large amounts of data between threads/processes ?
>|<*:=
[ Parent ]

Databases Are an Exception (3.80 / 5) (#38)
by moshez on Mon Nov 18, 2002 at 04:36:44 AM EST

Then again, databases have thorny issues of their own. It is positively hard to write databases. I am not at all certain, however, that using event-based processing for databases would not alleviate some of these constraints. Remember that databases, quite often, try to be "their own operating system", accessing the disk directly rather going through the, comparatively, less efficient OS buffering. Why not take this trend to the next higher level and do their own scheduling?

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Re: Databases Are an Exception (3.00 / 3) (#49)
by bugmaster on Mon Nov 18, 2002 at 04:57:29 AM EST

Why not take this trend to the next higher level and do their own scheduling?
One answer to that is that multiprocessing/threading is already implemented for you by the kernel, and it's faster than anything you could write, since it runs in kernel mode. Why reinvent the wheel ? However, I am not so sure that something like Oracle doesn't in fact implement its own scheduler... who knows how it really works :-) It does spawn multiple processes, though.

Anyway, I don't think databases are the only exception to the fallacious "threads are always bad" rule. What about Quake servers ? Application servers ? Or computationally intensive programs that want to take advantage of SMP (as someone else has pointed out) ? I don't think you can just dismiss threads (or processes with shared memory) out of hand like you did in the article.

Note, of course, that I am not saying, "threads are good for everything ! threads threads threads !" They are just a tool which is well suited to some jobs and not other, just like events, or screwdrivers for that matter.
>|<*:=
[ Parent ]

Threads are *Usually* Bad (5.00 / 2) (#55)
by moshez on Mon Nov 18, 2002 at 05:05:18 AM EST

What about Quake servers ? Application servers ? Or computationally intensive programs that want to take advantage of SMP

Let me address those:

Quake servers

I am not that familiar with the gaming world. However, many backend servers for games are event-based, which seems to suggest this is a good solution for at least some game servers. I am not familiar with the Quake server internals.

Application servers

This word has been bandied about so much I don't know what it means anymore. At the risk of guessing wrong, an application server is either something which runs applications on the server side, and does interaction for them with the client (seems to me the applications would be better implemented as processes) or it is a web application server. Web application servers can usually be easily written as event-based, because the web is inherently event based.

Applications which want to take advantage of SMP

If the tasks need to communicate so much that implementing it as seperate processes is not a good idea then, brother, neither is implementing them as threads.



[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Re: Threads are *Usually* Bad (3.00 / 2) (#58)
by bugmaster on Mon Nov 18, 2002 at 05:11:30 AM EST

I am not familiar with the Quake server internals
You can substitute "MMORPG servers" for those, if you want. Basically, how do you write a program that needs to provide many remote users with read/write access to large amounts of shared data ? Yes, you could write it with events, or hardcode it by hand in assembly or whatever, but with threads your program would usually be more readable and faster.

Web application servers can usually be easily written as event-based, because the web is inherently event based.
Same argument as above, really.
If the tasks need to communicate so much that implementing it as seperate processes is not a good idea then, brother, neither is implementing them as threads.
If the processes need to share memory, how are they better than threads ? And if you implement the application as just a single process, how does it take advantage of the other 3 CPUs that you have sitting on your motherboard ?
>|<*:=
[ Parent ]
Wrong Assumption (5.00 / 4) (#60)
by moshez on Mon Nov 18, 2002 at 05:13:17 AM EST

You keep assuming "communicate" means "share memory". Apparently, we're not managing to communicate, could you please ram your head forcefully through mine so that we may share memory? :D

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Communication (4.00 / 3) (#62)
by bugmaster on Mon Nov 18, 2002 at 05:16:55 AM EST

All communication (between processes, at least) seems to boil down to requests such as "I want to read that data you have", or "I want you to store this data for me". Yes, you may send a message to enable this communication, but, in the end, you are accessing shared memory.
>|<*:=
[ Parent ]
Indeed (5.00 / 2) (#66)
by moshez on Mon Nov 18, 2002 at 05:24:27 AM EST

We were not communicating. You seem to not be using the words "shared data" the way the rest of the world is using them. Would you mind, for the sake of this argument alone, if we used "shared data" to mean "some subset of the address space (maybe all of it) is shared", and use the word "communication" what you (seem to mean by) "shared data"?

With those definitions in place, let me answer: communication is better because each place in memory is changed by only one thread of execution. Thus, correctness is easier to ascertain.



[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Not Indeed (3.33 / 6) (#77)
by bugmaster on Mon Nov 18, 2002 at 06:15:36 AM EST

With those definitions in place, let me answer: communication is better because each place in memory is changed by only one thread of execution. Thus, correctness is easier to ascertain.
I am not sure if correctness is as easy to ascertain as it seems. For example, consider some sort of an MMORPG program, that needs to maintain the state of the persistent world. Let's say Bugmaster the Bearded casts "Fireball" on the monster, and Moshez the Mighty hits it with the ice sword. What happens ? Does the monster freeze or burn ? Does it do both ? Does the effect depend on the order in which messages were received ? What if it becomes important that frozen/burned monsters suffer extra damage due to rapid heat stress ? How do we program this ?

I realize that you can answer all these questions, but this is the same type of problem that you would have to deal with in a threaded program. If you can solve it in one case, you can solve it in the other case too.

Furthermore, sending messages back and forth is not always the best way to communicate, performance-wise. If we encode the entire array we want to send into the message, our event system bogs down. If we break up the array into tiny chunks, our event system bogs down in a different way. If we send a pointer to the array... er, we can't, because we don't share memory. Once again, I am sure you have an answer to these problems, but that just shows that you're smarter than the average programmer, not that events are neccessarily better.
>|<*:=
[ Parent ]

I'd Prefer Any Day (5.00 / 3) (#79)
by moshez on Mon Nov 18, 2002 at 06:26:38 AM EST

...To deal with correctness issues than performance issues. In the example above, it is quite likely that the answer to the monster question will fall out of the code naturally -- and Do The Right Thing(TM): if the events are close by, it will depend on so many small factors as to make the result appear random (as it should be). Nonetheless, it is very easy to ascertain that a sword hit actually lowers the monster's HP. In a thread-based system, even this simple invariant needs careful looking at: do all the places which modify HP use the lock correctly?

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Monsters (3.50 / 4) (#85)
by bugmaster on Mon Nov 18, 2002 at 06:42:31 AM EST

it will depend on so many small factors as to make the result appear random (as it should be)
Actually, in the original comment I specified that (fire and ice in rapid succession) do more damage than just (fire + ice), due to the added expansion/contraction shock on the monster. So, the result is decidedly non-random.

In a thread-based system, even this simple invariant needs careful looking at: do all the places which modify HP use the lock correctly?
This is actually pretty easy to ascertain. For example, in Java you can synchronize the "damageMonster" method, and be done. In C/C++, you can manually do the same thing with locks. This would be just as easy to implement in an event-based system.
>|<*:=
[ Parent ]
Um (4.20 / 5) (#90)
by moshez on Mon Nov 18, 2002 at 07:10:43 AM EST

Calling a damageMonster() method (which, even if inlined, has to lock;decrement;unlock, and in Java will be inlined only in certain borderline cases and in C++ to be inlined has to be in the header file) is somehow *more efficient* than "monster.hp -= 10" or whatever?

Re: fire and ice: Ok, I misunderstood your spec. Again, the straight forward code would just work in an event-based system: after a fire attack, set .attackedByFire = time(), after a snow attack, set .attackedBySnow =time(), during a fire attack, check the difference between time()-.attackedBySnow, mutatis mutandis for fire. How is that non-trivial? Actually, how did that require me to think any more than what I had to think about if the game was a single-threaded single-player game and the gamer had the option of quickly pressing the "snow" and "fire" buttons in quick succession?

Your own examples show me that event-based programming is indeed simple and straightforward :)



[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Whoa (3.20 / 5) (#94)
by bugmaster on Mon Nov 18, 2002 at 07:26:05 AM EST

Calling a damageMonster() method... is somehow *more efficient* than "monster.hp -= 10" or whatever?
It's not more efficient than "monster.hp -= 10", but then, neither is event-based programming. Events are not magic - someone still needs to create the "monsterHit" event, fill in the correct values, put it on the queue, then eventually take it off the queue, extract the values, call the appropriate event handler... etc. etc. Efficiency-wise, concurrency will always be slower.

Actually, how did that require me to think any more than what I had to think about if the game was a single-threaded single-player game...
I think you would agree that the "fire+ice==more damage" requirement makes things more complicated. You were smart enough to realize that you need to implement your code so it works "mutatis mutandis" for fire and ice -- some people would not be able to figure that out. You also created some new state on your Monster object, to keep track of the last attack that hit the monster. That state needs to be maintained correctly, regardless of implementation.

Now, consider what happens if credit for kills becomes important, as it almost always is in such games. Let's say the monster has 11 HP left; fire/ice attacks alone do 5 damage against it, but the combo attack would do 15 damage. If Bugmaster and Moshez execute the combo attack, who gets the credit ? What if the monster only had 3 HP left, who gets the credit then ? What happens to the ice attack if the fire attack kills the monster -- do you get a null pointer exception when you try to resolve it ? What if the ice attack was scheduled first, but got resolved last ? Come to think of it, how do attacks from concurrent Internet-based users get into the queue ?

These are just some of the questions you have to answer when programming in a concurrent environment, regardless of which model you use. The fact that you can say "well, you just do X" shows that you are a competent programmer who can spot all these issues before they happen -- not that your model is neccesarily better. A programmer who is unaware of these issues will run into difficult to find bugs with both the event model and the thread model. For example, the "I hit the monster and the server segfaulted ! But it only happens sometimes..." problem would occur in both cases.
>|<*:=
[ Parent ]

Again (4.16 / 6) (#97)
by moshez on Mon Nov 18, 2002 at 07:37:18 AM EST

I fail to see how this is different when working with event-based or threads. You seem to be arguing that (taken to the extreme) event-based programming does not solve world hunger, brings global peace or makes all programming problems trivial. I don't think that: programming will continue to be challenging for a while (of course, I may be wrong, and the solution that will solve all programming problems is just around the corner). My point is that threading adds to the inherent (say) algorithmic and specification difficulties of specifying "how much damage the monster takes, and who is credited for the kill" the added non-essential difficulty of handling multiple independent threads of execution.

When trying to decide if the monster was killed, and by whom, in my code, the absolutely last thing I want to do is figure out how to avoid a deadlock when I need to lock both the warrior's and the mage's "last person they were seen fighting with" attributes (because, of course, these must be consistent, so I must change them together).



[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Re: Again (3.20 / 5) (#187)
by bugmaster on Mon Nov 18, 2002 at 04:27:47 PM EST

I fail to see how this is different when working with event-based or threads.
That was my point: it's not. Concurrency is just hard, period, regardless of how you implement it.

You say that threading adds extra complexity on top of the usual algorithmic complexity of your program, and that is true. However, event-based programming adds the same complexity: I still need to deal with multiple cases, depending on which order operations are executed in; I still need to make sure that no one tries to hit a dead monster, etc. etc.

As I said before, I think your real issue is not with threads (or processes sharing mmaped data), but with poor language support for threads in Unix. Consider this for a moment: what if the basic event-processing infrastructure that exists in Win32/Unix/whatever was not conveniently implemented for you ? Then, you would have to write the queue manager yourself, including the multithreaded/multiprocessing part of it that deals with receiving events from multiple sources (mouse, display, modem, etc.). Or you could use polling... or hardware interrupts... or are those the same as threads ? The problems keep growing.

The situation above might be an exaggeration, but it is close to the state of threading support in C/C++. By contrast, threading support in Java is much simpler (as I said previously); in most cases, it boils down to the "synchronized" keyword. You have said before that threading requires you to change your program in "unnatural" ways. However, I have found that a correctly written OOP program lends itself quite easily to threading, since all the little code blocks and objects are already separated into logical groups. Event-based programming, however, sometimes feels unnatural to me.

In the end, I think the issue boils down to two things: language support and personal preference. Of course, there are some applications for which certain languages/concurrency models are better than others, but I think that your original claim that "threads are always bad in all cases" is way too strong.
>|<*:=
[ Parent ]

Concurrency *is* Hard (5.00 / 1) (#267)
by moshez on Tue Nov 19, 2002 at 01:39:11 AM EST

Event-based programming lets me deal with the inherent problems in coding the game engine. Threaded programming forces me to deal with serialization issues myself (synchrnoize takeDamage. synchronize getHitByFire. synchronize getHitBySnow. Think about whether these synchs are enough. What happens if the thread switches between takeDamage and getHitByFire? What order should they be in). Yay.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Ratings courtesy note (4.00 / 3) (#366)
by Rogerborg on Wed Nov 20, 2002 at 07:50:40 AM EST

Notice that my ratings on bugmaster's comments dropped from 5 to 1 by the end of this discussion, because the positions seemed to solidify as:

moshez: it's a tricky situation, so don't make it more tricky by using threads (tricky1 + tricky2 > tricky1).

bugmaster: it's tricky anyway, so using threads doesn't make it worse. (tricky1 + tricky2 <= tricky1)

Maybe I'm missing something, but I believe that moshez has a valid, pragmatic point, and that bugmaster doesn't.

"Exterminate all rational thought." - W.S. Burroughs
[ Parent ]

Re: Ratings courtesy note (none / 0) (#419)
by bugmaster on Fri Nov 22, 2002 at 03:19:13 PM EST

Well, I rated your comment at 5, just for the informativity (er... is that a word ?)

In general, I was basically saying the same thing that another poster said later on: threads get to applied to problems which require concurrency, which are always more difficult than most other problems, so the difficulty of threads is more correlation than causation. In addition, most languages expose many implementation details of threads, while hiding the concurrency issues involved in their event queue managers. And, finally, I do not believe that any article that says "you should not use X, ever" is correct, regardless of what feature X stands for. There are reasons someone invented that feature X, after all.

But you're right; other posters have expressed this more eloquently than I have, so there doesn't seem to be much point in continuing the discussion on this thread.
>|<*:=
[ Parent ]

Split the points among party members (5.00 / 1) (#153)
by pin0cchio on Mon Nov 18, 2002 at 12:14:10 PM EST

Now, consider what happens if credit for kills becomes important, as it almost always is in such games.

The "Pokemon" RPG for Game Boy solves this easily: every member of your party who participated in the fight gains some experience. Whether or not an attack contributed to killing a monster has been solved by "Klax": if the attack began while the monster had at least 1 health, it counts as participation.

Moral: Always specify your rules as completely as you can before you implement. The more bases you cover in design, the fewer problems you'll uncover in testing.


lj65
[ Parent ]
Urgh (none / 0) (#364)
by Znork on Wed Nov 20, 2002 at 07:09:59 AM EST

You just committed an atrocious programming practice I've started to place at about the same level as gratuitous use of threading.

monster.hp -= 10

I spent almost a week tracking down a bug in some caching code once. Strange problem and it only appeared to occur during high load, resulting in abruptly terminated connections. Someone had been saving his keystrokes a bit and written

buffer.position -=1

except, of course, what he'd actually written was

buffer.position =-1

And since the buffer didnt get used until there were slow connections and large amounts of data to be sent... I'd even been over the code several times before I saw that one, even while wondering why the hell the buffer position got set to -1.

Oh, well, that one's made it to my list of things to grep for and change throughout any C source I'm debugging these days.

Funnily enough, the bug was in an eventbased MMORPG.

[ Parent ]

[OT] If you can't stand the heat... (none / 0) (#369)
by czth on Wed Nov 20, 2002 at 09:32:31 AM EST

You just committed an atrocious programming practice I've started to place at about the same level as gratuitous use of threading.

monster.hp -= 10

  1. If you want Pascal, you know where to find it.
  2. If you can't stand the heat, get out of the kitchen.
C's op= operators are a language feature. If you don't think you can handle using them, that's fine, just don't try to pretend that this is some sort of sensible rule for those that can handle them. Might as well recommend against using == and using e.g. if(!(a-b)) rather than if(a==b) (sure, if one value isn't assignable you can put it on the left, but that won't always be possible).

And since the buffer didnt get used until there were slow connections and large amounts of data to be sent... I'd even been over the code several times before I saw that one, even while wondering why the hell the buffer position got set to -1.

Set a watch on the variable. Duh. Or, as a preliminary measure, just grep for 'buffer' on the same line as '-1' in the source.

Oh, well, that one's made it to my list of things to grep for and change throughout any C source I'm debugging these days.

Not if you worked with me you wouldn't be gratuitously changing my code, even if you were debugging a module I wrote.

czth

[ Parent ]

Misfeatures. (none / 0) (#374)
by Znork on Wed Nov 20, 2002 at 11:16:56 AM EST

They're not a language feature, they're a misfeature. The days when one byte or one keystroke would save measurable space are over.

It's not a question of being able to handle it, it's a question of programming habits that are less likely to produce errors. The programmer who wrote the original code was certainly able to handle it, I'm certainly able to handle it. But when you're scanning and/or writing code fast, because of the way human pattern recognition works you're going to miss things like this every now and then. Which makes it prone to errors. Unnecessary errors.

Finding the error once it was located didnt take as long. The problem as handed to me was 'client loses connection', which tracked down to 'packets become garbled' which was a pita to discover because they didnt become garbled when debugging. Nor did the server crash or indicate any error of any sort other than the client terminating the connection. Breakpoints and watches were useless in tracking it down since it didnt even reach that code under testing conditions. But after a lucky break in finding where the problem was, setting a watch on the variable worked (after rewriting some code to force the server to use the buffering).

Simple, yet very annoying and entirely avoidable by writing as readable code as possible.

[ Parent ]

I'm not quite getting your question here. (5.00 / 3) (#102)
by glyph on Mon Nov 18, 2002 at 08:15:26 AM EST

I realize that you can answer all these questions, but this is the same type of problem that you would have to deal with in a threaded program. If you can solve it in one case, you can solve it in the other case too.

In the event-based example, we have to one question to answer: for a given serialized stream of "takeDamage" messages, what is the correct behavior of the monster? In a threaded example, we have to answer the same question even if the events are actually happening at the exact same time.

  • Event-based
    • Solve the problem.
  • Threaded
    • Determine that all locks are in place to properly serialize all events so that we can tell what order they're happening in and how far apart they're happening.
    • Solve the problem.

So as you've phrased your example here, it sounds like you have exactly twice as much work to do here if you're using threads, not counting the additional difficulty of testing.

Furthermore, sending messages back and forth is not always the best way to communicate, performance-wise.

If you're modelling a simulation, you really don't have any other options. If you're talking about sending bytes over a pipe or TCP socket, then perhaps there are more efficient ways to represent a 'message'. However, you need to do some kind of synchronization around the bytes that represent the message, otherwise it's going to get garbled in transit. (By the way: benchmark pipes on your favorite Linux or FreeBSD system some time . They're faster than you think.)

Since these events affect the same objects, though, and there's pretty much no way to get around that (both Moshe the Mighty and Bugmaster the Bearded gain experience: the monster does take damage) the best-performing way to architect this, especially on a single-CPU system, is as a series of method calls in a synchronous process. The tricky part is getting the messages there in the first place, but you're going to have to deal with the inefficiency of byte streams there anyway: the players expressing these intents are on different computers that are far away, and there's no way you can optimize the user out of the picture.

In practice it's not that hard to make sure that users engaging in combat or whatever all happen to be on the same server at the time. In the cases where you can't, just trying to get them on the same server and then falling back to sending encoded messages in the cases where they're not is fine.



[ Parent ]
Mostly Incorrect (4.75 / 4) (#98)
by glyph on Mon Nov 18, 2002 at 07:54:47 AM EST

You can substitute "MMORPG servers" for those, if you want. Basically, how do you write a program that needs to provide many remote users with read/write access to large amounts of shared data ? Yes, you could write it with events, or hardcode it by hand in assembly or whatever, but with threads your program would usually be more readable and faster.

The more users you have, the worse of a problem threads become. Consider the performance problems with a thread-per-connection strategy. MMP servers do use threads, but not for what you're suggesting. They do it essentially to squeeze the highest performance out of the kernel networking primitives on SMP systems.

A typical strategy is to have an IO thread and a game logic thread, which communicate exclusively through a single queue, emulating CSP style. The IO thread's purpose? Generate events as fast as possible for the event-based game thread to handle.

To actually take advantage of additional CPUs on your cluster machines, you run additional processes. My heuristic experience so far is that the I/O thread is not actually necessary and is based on outdated assumptions about kernel performance ephemera which were only true on solaris anyway. I have yet to deploy my own MMP written in a purely event-based manner, but the way they generally work is basically "event based, plus some functionality that the OS forgot".



[ Parent ]
Quake 3 client (none / 0) (#261)
by DodgyGeezer on Tue Nov 19, 2002 at 12:46:54 AM EST

Talking of Quake, what about Quake clients? Quake 3 runs very nicely on my SMP box. I think it's a simple producer-consumer relationship with one thread dedicated just to the graphics... no matter how busy the game gets, the graphics seem to remain much smoother without the framerate dropping off so drastically.

[ Parent ]
OK, I'm Fed Up With This (3.00 / 2) (#288)
by moshez on Tue Nov 19, 2002 at 03:32:44 AM EST

Look: many operating systems (including most production versions of Linux) cannot use SMP with threads: threads of the same process will run on the same CPU. This makes threads, quite often, complete useless for utilizing SMP.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
If Your Operating System is Crippled... (none / 0) (#325)
by DodgyGeezer on Tue Nov 19, 2002 at 10:31:09 AM EST

...you need crutches.  

Touché

The fact of the matter is, Windows is on more than 90% of desktops and an increasing number of servers, so you can't exclude it from a discussion about threads just because most production Linux implementations aren't very good at threads.

[ Parent ]

Yes I Can :) (none / 0) (#328)
by moshez on Tue Nov 19, 2002 at 10:55:06 AM EST

Watch me...

Seriously: Yes, on Windows, many times, threads are the only sane alternatives. In fact, I had the opportunity to ask people who work in Microsoft, and they assured me Windows will never have a fork. To me, this means one thing: Windows is doomed. Anyone who codes to Windows codes to a sucky platform, in which case my only advice is to a) roll with the punches and b) avoid it. I usually avoid supporting Windows by writing server-side applications.



[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Then stop eating it (none / 0) (#335)
by roystgnr on Tue Nov 19, 2002 at 11:40:43 AM EST

The fourth sentence on the Linux Threads FAQ:

"The default Linux thread implementation is with kernel-space threads, not user-space threads; these threads will schedule properly on an SMP architecture."

Do you have some information to the contrary?  I think the current schedulers try to put threads on the same CPU when the other CPU can be used by another process, but I've never heard of it letting one CPU sit idle when there are two runnable threads.

[ Parent ]

Databases are an Exception? (2.00 / 2) (#252)
by greenrd on Mon Nov 18, 2002 at 11:03:27 PM EST

Haha, that's a good one. "Databases are an exception".

That sounds like as if a Haskell programmer were to say, when asked whether user interaction isn't a side-effect, "Oh, user interaction is an exception which we can ignore for the purposes of this argument".

Database programming is everywhere in the real world - outside of academic ivory towers. Yes, not everyone programs DBMSs - but many, many people write or maintain multithreaded code that maintains shared persistent (and non-persistent) state in a database. You can't just shrug these off as piddling little "exceptions"!


"Capitalism is the absurd belief that the worst of men, for the worst of reasons, will somehow work for the benefit of us all." -- John Maynard Keynes
[ Parent ]

You Misunderstood Me (4.00 / 2) (#266)
by moshez on Tue Nov 19, 2002 at 01:36:09 AM EST

Programming database engines is an exception. Programming code that works with database engines is not an exception. In fact, such code can use the fact that the database (assuming it's a real ACID database and not a highschooler's toy) is so powerful by letting processes do what context sharing they want to by using the database. Design sounds familiar? No? Look around you, this is how Scoop is written. *waves to rusty*

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
SQL Server uses a fiber-based scheduler (none / 0) (#399)
by senderista on Thu Nov 21, 2002 at 03:56:42 PM EST

I don't know about other RDBMS's, but this is precisely what SQL Server does - it has its own scheduler, using the NT fiber API. Of course, this "scheduler" still doesn't preempt the system scheduler, as a fiber will be preempted when its thread is preempted. I'm not aware of any equivalent to fibers in the UNIX world, although to be fair they're not much more useful than ordinary threads.


"It amounts to the same thing whether one gets drunk alone, or is a leader of nations." -- Jean-Paul Sartre
[ Parent ]

Perhaps I wasn't clear. (3.50 / 4) (#111)
by _Quinn on Mon Nov 18, 2002 at 09:13:05 AM EST

The example you gave in the article was bogus.  Nobody in their right mind would simply start inserting locks more or less at random, as you suggest: in particular, your example becomes problematic precisely because the synchronization isn't refactored from the four different lines in which it occurs to the two places it's actually needed.  Furthermore, the solution to remembering lock ordering constraints is to code them once and use them everywhere, not insert some global lock which doesn't address what the programmer knows to be the real issue.  Just because it's a threading issue doesn't mean people are going to abandon the good practices they diligently learned elsewhere.  If they don't have any, they're violating rule two, and nothing will save them anyways -- threading or no threading.

- _Quinn
Reality Maintenance Group, Silver City Construction Co., Ltd.
[ Parent ]

Do You Honestly Think (3.00 / 2) (#127)
by moshez on Mon Nov 18, 2002 at 11:02:03 AM EST

That the example I gave in the article is unrealistic? You know too few programmers, then.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
I _am_ a programmer. (5.00 / 3) (#184)
by _Quinn on Mon Nov 18, 2002 at 04:17:29 PM EST

You draw a parallel between threads and gotos but fail to follow it out: gotos were harmful not because they weren't useful, but because it was too difficult to harness their utility without proper language support, that is, structured programming.  I believe a similar situation exists with threading; you can see an example of the progression with Java.  I argue, perhaps as proponents of gotos did, that bad programmers will write bad programmers regardless, so the utility of threading is not diminished by their incompetence.  I agree, however, that structured programming is a Good Thing; the equivalent for threading should be as well.

- _Quinn
Reality Maintenance Group, Silver City Construction Co., Ltd.
[ Parent ]

Your cynical example (4.00 / 1) (#296)
by ajf on Tue Nov 19, 2002 at 06:36:41 AM EST

Can you convince me that your hypothetical idiot wouldn't attempt a blocking I/O call during event processing? Would he know what to do to keep the system responsive if an event kicks off some computations which take fifteen seconds to process?

Certainly threaded programming has its problems, like the ones you describe, but event-driven code has pitfalls of its own, and they're not trivial concerns either. Probably every GUI application I have ever used can get into a situation where the user interface stops responding entirely, because for some reason the application isn't processing the events the platform is trying to send to it.

"I have no idea if it is true or not, but given what you read on the Web, it seems to be a valid concern." -jjayson
[ Parent ]

Of Course He Will (none / 0) (#297)
by moshez on Tue Nov 19, 2002 at 06:42:59 AM EST

In a different comment, I explained that blocking I/O inside an event handler is much more easily analysed and fixed -- it does not need a heavy load to bring it about, and it is easy to step in with a debugger and see where the code is spending its time.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Or maybe you know too many unrepresentative ones (none / 0) (#303)
by Kalani on Tue Nov 19, 2002 at 07:20:41 AM EST

I mean, as long as we're just going to throw around anecdotal evidence without anything hard to back up our assertions with, then both of you are wrong and I'm afraid that most programmers are just orange blobs who sit around throwing darts at design specs. Duh.

-----
"Images containing sufficiently large skin-colored groups of possible limbs are reported as potentially containing naked people."
-- [ Parent ]
I See (5.00 / 2) (#305)
by moshez on Tue Nov 19, 2002 at 07:26:12 AM EST

So you're saying there is a reason why I'm covered by darts stuck in me, each with a bit of orange goo at the end?

And I was going to chalk that up to paranoid delusions. Thanks, man!



[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Single CPU systems can have parallelism (5.00 / 2) (#210)
by Sloppy on Mon Nov 18, 2002 at 06:33:26 PM EST

It is not a runtime speed optimization if you do not have more than one CPU. It sounds obvious; people forget it anyway.
Only for a very liberal definition of "CPU". :-) Anything that can get work done independent of one CPU (whether it's another CPU, a computer over a network, or a disk that can DMA transfer) counts.

For max performance, as soon as this other non-CPU entity finishes something, you want a thread to wake up ASAP and make start another unit of work (e.g. read the next block off the disk, request the next bunch of packets, etc).

A classic example is an Amiga playing an animation off a floppy drive (where the chipset can read the floppy and DMA the data into RAM without much help from the CPU). Even with just one CPU, you get a major speedup by having one task do the disk I/O while another plays the animation. (What else are you gonna do? Read a frame, play it, read a frame, play it? Ewww!)
"RSA, 2048, seeks sexy young entropic lover, for several clock cycles of prime passion..."
[ Parent ]

Hey wait, I think I get it (4.50 / 4) (#214)
by Sloppy on Mon Nov 18, 2002 at 07:00:29 PM EST

After reading some of these comments, I think I can predict what moshez would say about my example: use separate processes for the disk reading and the animation playing, and connect 'em with a pipe. Then, I don't have to put a lock on the RAM buffer (it is essentially replaced with the pipe), so the program becomes slightly simpler, and harder to accidentally add bugs to.

Hm.
"RSA, 2048, seeks sexy young entropic lover, for several clock cycles of prime passion..."
[ Parent ]

Wow (3.66 / 3) (#265)
by moshez on Tue Nov 19, 2002 at 01:33:42 AM EST

The orbital lasers seem to be working just great! Mental note: tip the illuminati, such service should be rewarded. Another mental note: get them to erase memories of K5ers that this post ever existed.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Wrong (4.00 / 1) (#248)
by greenrd on Mon Nov 18, 2002 at 10:55:16 PM EST

It is not a runtime speed optimization if you do not have more than one CPU.

False. Consider GUI responsiveness.

For example, allowing the user to abort a task earlier means a very concrete and actual efficiency increase.

Yes, GUI responsiveness can be improved "without using threads". But so what? That doesn't change the fact that threads can be used to increase performance.

By the time you have proper abstraction, you've killed performance.

That's a ridiculous overgeneralisation.

but if you are only sharing a small amount of information, I would ask why you are using a shared memory space at all.

Because it's convenient?


"Capitalism is the absurd belief that the worst of men, for the worst of reasons, will somehow work for the benefit of us all." -- John Maynard Keynes
[ Parent ]

True (none / 0) (#260)
by DodgyGeezer on Tue Nov 19, 2002 at 12:34:34 AM EST

I was working on something earlier in the year that would run a task for up to 15 minutes. This has to be either multi-threaded, or multi-processed. If not, the processing occurs in the event loop of the UI, which makes the program look like it's crashed. Secondly, allowing user cancellation is much harder as it involves period checkpoints that examine the event queue rather than check if an event (semaphore) is signalled, and there is no immediate feedback to the user.

[ Parent ]
And What's Wrong With Multi-process? (none / 0) (#264)
by moshez on Tue Nov 19, 2002 at 01:32:35 AM EST

Usually, my experience with long running computations is that they end up coming with a fairly short "result". This can be easily done via forking a process (a very quick operation on UNIX) and opening a pipe. This makes sure the long computation cannot, by any chance, scribble over the parent's address space -- and with COW, without paying the price for copying it. This brings to mind Clark's words about sufficiently advanced technology, I'd say.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Win32, not UNIX (none / 0) (#274)
by DodgyGeezer on Tue Nov 19, 2002 at 02:06:08 AM EST

Because this was implemented under Windows and it would confuse more people during the maintenance cycle than a thread does. It's not the done thing to do. Besides, posting a message with a pointer to the results couldn't be easier. I guess this makes it a hybrid multi-thread and event-driven approach. The windows message/event queue synchronises task progress updates, and the only synchronisation object is a event/semaphore used for task cancellation. I fail to see why I would want to go to all the effort to spawn another process... that could cause even more unknowns, such as the user seeing it in task manager and killing it. Oh, and the result wasn't fairly short either ;) And as for the thread writing all over the parents address space... that's hardly a threading issue in this situation.

[ Parent ]
If Your Operating System is Crippled... (1.00 / 2) (#275)
by moshez on Tue Nov 19, 2002 at 02:13:44 AM EST

...you need crutches.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
COM (none / 0) (#276)
by DodgyGeezer on Tue Nov 19, 2002 at 02:17:22 AM EST

Oh, and under Windows, if I really wanted something out of process, I would use COM, not CreateProcess and use some sort of IPC like a pipe. So long as I stick with data types that the universal marshaller knows about, it's just a matter of calling methods on an interface without knowing whether it's in or out of proc. Simple... but still more effort than this situation would warrant. I see having to communicate with a child process via a pipe as much too low level... unless there are some object libraries that save me using things like fprintf and having to worry about keeping to a communications protocol.

[ Parent ]
Yes (none / 0) (#277)
by moshez on Tue Nov 19, 2002 at 02:22:43 AM EST

Using libraries is a Good Thing. One of my favourite portable remote object libraries is a part of Twisted -- Perspective Broker. Never worry about serialization again!

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Heh (4.00 / 1) (#322)
by kuran42 on Tue Nov 19, 2002 at 09:45:01 AM EST

That's an interesting definition of "optimization" you've got there. I don't consider GUI responsiveness worth discussing; it's a trivial problem. Can threads help? Yea, I guess. Can simpler solutions? You betcha. I have no problem with long running calculations (that the user can cancel at any time, by the way) interfering with any of my GUI apps.

Yes, GUI responsiveness can be improved "without using threads". But so what? That doesn't change the fact that threads can be used to increase performance.

Maybe threads provide a benefit over a naively written program that blocks the GUI for long periods of time, but I doubt you'll show me any significant improvement over, for example, a single threaded application using a resumable function for its long running computations.

--
kuran42? genius? Nary a difference betwixt the two. -- Defect
[ Parent ]

Development time (none / 0) (#329)
by DodgyGeezer on Tue Nov 19, 2002 at 10:57:24 AM EST

Yes, but how much effort (including QA, bug fixing and maintenance) does it take to make a process resumable compared with the simpler but more dangerous multi-threaded approach?  In many situations, the threaded approach is simpler and easier to deal with.  In my experience.

[ Parent ]
Indeed (3.75 / 4) (#7)
by moshez on Mon Nov 18, 2002 at 02:32:02 AM EST

I thought the article covered such a "single lock easy solution" by explaining that this causes serialization. So you have all the pain of a single-thread program, and all the pain of a multi-thread program. A win-win situation.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Not so (4.00 / 1) (#229)
by gnovos on Mon Nov 18, 2002 at 07:53:45 PM EST

Well, if all threads had to read the exact same data at the exact same time, sure you then have serialization....  but most likely this will not happen except every so often.  so 90% of the time you have a nice multithreaded program and 10% of the time it drops down to serialized execution.

A Haiku: "fuck you fuck you fuck/you fuck you fuck you fuck you/fuck you fuck you snow" - JChen
[ Parent ]
Did You Actually *Read* My Comment? (none / 0) (#287)
by moshez on Tue Nov 19, 2002 at 03:30:21 AM EST

I said "if there is a single lock", the program is serialized. This was in reply to a comment suggesting a single lock, so, funny me, I thought it relevant to point out this problem.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
a lesson many people learn too late (4.54 / 11) (#8)
by washort on Mon Nov 18, 2002 at 02:43:47 AM EST

I wish more network programmers would be exposed to this sort of thing earlier in their programming career; as you say, threads seem like the obvious and easy answer until it's too late :) I've met programmers who think 100 clients is a heavy CPU load for a server, merely because they dont know how to write concurrent apps any way other than thread-per-socket, which is a tragedy.

Not necessarily bad... (2.00 / 3) (#121)
by jholder on Mon Nov 18, 2002 at 10:48:41 AM EST

Thread per socket isn't necessarily bad, as long as each thread can service multiple users

[ Parent ]
Coffee sunk in. Nevermind. (3.50 / 2) (#124)
by jholder on Mon Nov 18, 2002 at 10:51:36 AM EST

obviously I'm not awake... since each user is a seperate socket, you can't have a thread per socket handle multiple users. Doh!

/me slurps more coffee.

[ Parent ]
reference? (none / 0) (#404)
by codeslut on Thu Nov 21, 2002 at 10:21:08 PM EST

What would you say is a good reference for learning network programming then?

Thanks.
-----
"`The Kerastion is a musical instrument that cannot be heard`.
Now there's a Borges story in ten words!"
- Ursula K. Le Guin
[ Parent ]

What do you suggest? (none / 0) (#440)
by hornsby on Thu Dec 05, 2002 at 02:23:26 PM EST

I wrote a multiuser proxy server in Ruby a few months back and made use of Ruby's builtin pseudo-threads. My program dedicated one thread to each connection because it seemed the simplest solution, and the server will never have more than 2 to 3 users in the context I'm using it in. I'm interested in what model you suggest as an alternative to the thread-per-socket. Select with file descriptors? Fork? I'm a newbie to writing servers, so I'm always trying to learn.

[ Parent ]
Threads ain't harmful (2.62 / 8) (#9)
by dopehead on Mon Nov 18, 2002 at 02:44:22 AM EST

It isn't the thread that kills. It is the man who puts the thread around your neck and pulls it tight.

Every powerful tool has to be used with care, because they are all harmful.

Give a man a compilation tape and he'll dance for a night. Teach a man to scratch, and he'll be dancing for generations!

However (4.00 / 4) (#10)
by moshez on Mon Nov 18, 2002 at 02:48:14 AM EST

Threads may not kill people, but they do make it awefully easy to shoot yourself in your foot. The point is that modern programming techniques developed mostly to let the programmer know he is going to shoot himself in the foot before he does. Threads have an annoying tendency to bypass all the safeguards and you end up with too many one-footed programmers.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Just one question. (3.40 / 5) (#11)
by i on Mon Nov 18, 2002 at 02:53:35 AM EST

Are you Robbert van Renesse?

Nah, don't think so. He provides specific examples. He gives numbers. What do you have to match? Nothing I'm afraid.

You've got a classical deadlock problem. Lo and behold! none of your "solutions" will address it properly. The coroutines "solution" and the event-based "solution" are particularly bad. Why? My server has 8 CPUs. 'Nuff said.

Multiprocess "solution" won't do well in many sutuations, too. It may work for things like Apache where request are largely independent. If you have to program a DBMS things will look rather differently.

and we have a contradicton according to our assumptions and the factor theorem

I Did Not Give Numbers (3.50 / 4) (#12)
by moshez on Mon Nov 18, 2002 at 02:56:57 AM EST

This article was supposed to be a somewhat humourous take on the problem. In many cases, your 8 CPUs will go to waste without further thinking. For example, in Python, my programming language of choice, the GIL would kill any benefit you have. Indeed, with multiple CPUs the best way is often to partition the problem into several co-operating programs, each with its own address space. In web servers, the easy way to do it is via reverse proxying to a round-robin of (n-1) servers sitting on different ports (n being the number of CPUs).

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Huh? (2.50 / 2) (#20)
by i on Mon Nov 18, 2002 at 03:32:22 AM EST

in Python, my programming language of choice, the GIL would kill any benefit you have.

This makes Python a wrong tool for many jobs.

the best way is often to partition the problem into several co-operating programs

Not everything is a web server, you know.

and we have a contradicton according to our assumptions and the factor theorem

[ Parent ]

Not Everything is a Web Server (5.00 / 2) (#21)
by moshez on Mon Nov 18, 2002 at 03:36:20 AM EST

I gave an example of how to partition a program into several co-operating processes with separate address spaces. There are many cases in which such a technique helps use multiple CPUs while avoiding the evils of threads.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Re: Not Everything is a Web Server (1.00 / 1) (#51)
by bugmaster on Mon Nov 18, 2002 at 04:58:58 AM EST

I asked this question before, but, for the sake of completeness: How do processes co-operate if they don't share some memory ? If they do share memory, how do they share it without locks or mutexes or semaphores, etc. ?
>|<*:=
[ Parent ]
When You Keep Using Definitions Differently (5.00 / 1) (#69)
by moshez on Mon Nov 18, 2002 at 05:36:32 AM EST

Then you run a chance of being misunderstood.

Usually, when hearding "shared data" I imagine some kind of public mmapped memory which is shared. When hearing communication I imagine things like pipes or unix-domain sockets.



[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Definitions (none / 0) (#82)
by bugmaster on Mon Nov 18, 2002 at 06:30:42 AM EST

Usually, when hearding "shared data" I imagine some kind of public mmapped memory which is shared. When hearing communication I imagine things like pipes or unix-domain sockets.
Fair enough; but which word should I use for "data which needs to be read from and written to by multiple concurrent users/clients ?" Because, regardless of implementation details, this is the kind of thing you need to provide if you are creating a Web server, a game server, a database, etc.
>|<*:=
[ Parent ]
I Suggested (4.00 / 1) (#92)
by moshez on Mon Nov 18, 2002 at 07:20:36 AM EST

"Communication", because that's the word I use for data which needs to be transmitted. Dunno, maybe I'm strange. Granted, different processes doing something in common sometimes need to communicate. One usually tries to keep the needed communication to a minimum through careful partitioning of the code into seperate tasks. Doing it incorrectly means a loss of performance. Like I said above, I'd prefer having an inefficient working program than having an efficient buggy program -- so I prefer any mistakes I make would cause the former rather than the later.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
not wrong so much as suboptimal as distributed (4.00 / 1) (#35)
by h2odragon on Mon Nov 18, 2002 at 04:31:25 AM EST

This makes Python a wrong tool for many jobs.

Unless of course you have the proper add on module. I wrote that for doing large concurrent work on a 12cpu machine; the python interpreter i use doesn't have threads at all.

[ Parent ]

Heh (none / 0) (#134)
by tzanger on Mon Nov 18, 2002 at 11:22:01 AM EST

???. ???

What's that say?

????

[ Parent ]
What's that? (none / 0) (#158)
by i on Mon Nov 18, 2002 at 12:44:55 PM EST

That's a banner on somebody's site which I happen to like.

and we have a contradicton according to our assumptions and the factor theorem

[ Parent ]
Well duh... (none / 0) (#165)
by tzanger on Mon Nov 18, 2002 at 01:59:00 PM EST

That's a banner on somebody's site which I happen to like.

Well duh. :-) What does it say?



[ Parent ]
It says (none / 0) (#170)
by i on Mon Nov 18, 2002 at 02:27:55 PM EST

"In the Promised Land they tore my arse apart". It rhymes in the original Hebrew.

and we have a contradicton according to our assumptions and the factor theorem

[ Parent ]
Rough Translation (none / 0) (#271)
by moshez on Tue Nov 19, 2002 at 01:54:24 AM EST

From a Hebrew speaker: "Brother, more power to you". Ok, this sentence can't be translated very well, but the above is a useful approximation :)

I should write an article of the futility of translation one of these days...



[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Ok so which is it (none / 0) (#318)
by tzanger on Tue Nov 19, 2002 at 09:27:22 AM EST

i says it says "In the Promised Land they tore my arse apart", you said it roughly translates to "Brother, more power to you." Is Hebrew really short on words or something? I'd be worried that saying "hello" would be interpreted as a terrible insult!



[ Parent ]
Both... (none / 0) (#336)
by roiem on Tue Nov 19, 2002 at 11:43:11 AM EST

The link says (approximately) "Brother, more power to you", but when you click on it and get to a banner, it says "In the Promised Land they tore my arse apart".
90% of all projects out there are basically glorified interfaces to relational databases.
[
Parent ]
-1 Ignorance "Considered Harmful" (4.15 / 20) (#14)
by omegadan on Mon Nov 18, 2002 at 03:01:09 AM EST

It's obvious you don't know anything about threaded programming ...

The pitfalls of multi-threaded programming are well understood both in theory and in practice. Using semaphores and locks to secure critical sections is a simple and elegant solution. The only danger is abuse from amateur programmers.

Furthermore, there are many situations where a mutli-threaded program is the best solution. Your "alternatives" are disasters. "Multiple processes" is the exact same thing as threads except now you are burdened with using semaphores or pipes to communicate, in the linux kernel there is no entity that corresponds to a thread, all threads are processes. What is the difference between calling a function which retrieves data from another process and calling a lock function? Absolutely nothing, except the lock function is orders of magnitudes faster.

Co-operative multitasking is and always will be a disaster for anything but simple or embedded applications.

Event based programs are the best solution you mention, but simply loony for most applications, they force you to structure your program in a cumbersome way. Event models are useful in programs that must deal with a variety of disparate tasks in real time (videogames, toolkits, word processors, communications systems), you'll notice they aren't widely used outside of this.

Your conclusion is faulty as well. It is true that threaded programming requires more care and more forethought, but this is engineering my friend, problems are not guaranteed to be easy to solve, fun, or to be described by any other positive adjective. Billions of threaded programs are running on computers everywhere as we speak, proof enough that the concept works and it works fine. Even worse then that, this is a simple problem with an elegant and simple solution; which leads me to believe you are either uniformed.

Religion is a gateway psychosis. - Dave Foley

Nonsense (4.50 / 4) (#15)
by moshez on Mon Nov 18, 2002 at 03:03:40 AM EST

There is a lot of difference, yes even on Linux, between processes and threads. You want to know what the difference is? seperate address space. Seperate address space is good: it means different processes can't scribble on each other's variables: they can only communicate in well-defined ways.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
wrong... (2.88 / 9) (#22)
by omegadan on Mon Nov 18, 2002 at 03:45:08 AM EST

Allow me to quote Moshe Bar, "The above check is in the core of the Linux kernel context switch. It simply checks if the address of the page-directory of the current process and the one of the to-be-scheduled-process are the same. If they are indeed, this means they are sharing the same address space (or in other words, they are two threads) and it avoids writing to the %%cr3 register, so the user-space page tables won't be invalidated at all. This happens because the cr3 just contains the data to which the kernel would write to (and writing to it would mean invalidating the TLB).

With the above two-line check Linux makes a difference between a kernel process switch and a kernel thread switch. This is the ONLY noteworthy difference"

Now read my quote ..."Multiple processes" is the exact same thing as threads except now you are burdened with using semaphores or pipes to communicate

Religion is a gateway psychosis. - Dave Foley
[ Parent ]

you little bitch (2.22 / 9) (#25)
by omegadan on Mon Nov 18, 2002 at 04:02:04 AM EST

Modding me a 1 when you've lost an argument is *not* cool. If you have something to say, refute my argument.

Religion is a gateway psychosis. - Dave Foley
[ Parent ]

I don't think so (3.75 / 4) (#40)
by ogre on Mon Nov 18, 2002 at 04:41:11 AM EST

I suspect that the reason you were modded down is because you said he was wrong and then gave a quote that directly confirmed what he said. How is he supposed to argue that? Maybe you just aren't familiar with "address space" terminology?

Everybody relax, I'm here.
[ Parent ]

it's really in how you read it. (none / 0) (#387)
by omegadan on Thu Nov 21, 2002 at 02:13:27 AM EST

the quote supported exactly what I said. For the third time, "Multiple processes is the exact same thing as threads except now you are burdened with using semaphores or pipes to communicate".

"Using sempahorse or pipes to communicate" clearly demonstrates my consideration of memory protection :) however, memory protection isn't worth garbage in this case, you can read my refutation of that here.

Religion is a gateway psychosis. - Dave Foley
[ Parent ]

I agree... (4.71 / 7) (#81)
by joto on Mon Nov 18, 2002 at 06:30:25 AM EST

Moshez has consistently modded me (who agree with him) up, and you (who disagree) down. That is not at all in good taste when it is his article.

Maybe we should start moderating his comments to 1 (you) or 5 (me) just for the heck of it?

Or perhaps we should switch, just to make it a bit more confusing?

[ Parent ]

wrong wrong (3.80 / 5) (#28)
by washort on Mon Nov 18, 2002 at 04:05:59 AM EST

"Multiple processes" is the exact same thing as threads except now you are burdened with using semaphores or pipes to communicate

Au contraire! the difference between threads and processes is that you receive the benefit of memory protection and serialization of communication into a pipe.

(hint: you are in vehement agreement with him)

[ Parent ]

in this situation (3.83 / 6) (#31)
by omegadan on Mon Nov 18, 2002 at 04:25:15 AM EST

Of course you are correct, but memory protection isn't going to help a fuckup of a programmer regardless. If the programmer can't write threaded code reliably, then he's really in no position to be using seperate processes either.

Its really a question of how you want to abstract the program. Lets say you have two portions of a program A and B. A and B are implemented both using seperate processes, and as a multithreaded application. Portion A has a bug. Two scenarios emerge:

Threaded App, Portion A crashes. Portions A and B die.

Multi-process App, Portion A crashes. Portion B is now blocking on I/O waiting for portion A, and will never see another CPU cycle. If its non-blocking, its eating CPU time checking for new data (maybe it could exit gracefully).

In both scenarios the problem is the *BUG* not the threading model. If your program is working correctly, neither A or B will crash. If it is malfunctioning in either model, both A and B still crash. Memory protection is a *burden* in any situation where two programs could be combined into one.

Religion is a gateway psychosis. - Dave Foley
[ Parent ]

Actually (3.66 / 3) (#39)
by moshez on Mon Nov 18, 2002 at 04:39:47 AM EST

There is nothing which helps a fuckup of a programmer more than memory protection, in my experience in working with quite a few fuckups of a programmers.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
help of memory protection (5.00 / 4) (#73)
by joto on Mon Nov 18, 2002 at 05:57:38 AM EST

but memory protection isn't going to help a fuckup of a programmer regardless. If the programmer can't write threaded code reliably, then he's really in no position to be using seperate processes either.

So let me ask you this. If you were handed someone elses program. Would you rather have a correct multithreaded one that you had to understand all of, or a correct multi-process one where you could easily spot every single instance of communication between separate processes? Where you could test parts independently? Where you could break stuff in one process and catch it in a debugger, without breaking all the others?

The problem isn't just writing it. The problem is to keep it working.

[ Parent ]

+1 Stupidity considered harmful (4.88 / 9) (#18)
by epepke on Mon Nov 18, 2002 at 03:18:04 AM EST

Damn, you got my title first. Oh well.

One of the results I was hoping for from the popping of the dot com bubble was the hope that incompetents would go into easier and more lucrative businesses. It seems not to have happened, though.

The deadlock, described but not mentioned by name in the article, is really freshman CS stuff. You're spot on about multitasking and multiprocessing; if you have the discipline to do that, it doesn't cost any more (usually) to map them onto threads.

I still think the article has value, though, because in a way, threads are a lot like goto. Most of the time, they aren't the best solution, but when they are, they are really the best solution. The trick is knowing when they are the best solution. When I worked at the Supercomputer Computations Research Institute, we had a 16-processor shared-memory machine, and multithreading was the best solution in that case for scientific computational code. We also had a cluster of a couple of hundred machines linked with FibreChannel, and in that case, individual processes that communicated in a variety of ways (we used Linda for some apps and ad hoc mechanisms for others) worked really well. With looser and less symmetric connections, a basic IP approach with separate processes worked OK (but datagrams were a lot faster).

As a side issue, I had a code with about a quarter million lines, and it has maybe 20 goto's. Probably ten of them aren't strictly necessary, but the remaining ten are just where they should be.


The truth may be out there, but lies are inside your head.--Terry Pratchett


[ Parent ]
I Actually Agree (4.20 / 5) (#19)
by moshez on Mon Nov 18, 2002 at 03:21:09 AM EST

The thing is, the people who know their program actually needs threads will know enough to take this article with the appropriately-sized grain of salt. And the people who don't know that should not be using threads.

The article did say that there is a limited class of problems for which threads are appropriate. My problem is that, like goto, they are most often used inappropriately be people who do not know better.



[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
I don't like your approach (3.40 / 5) (#26)
by nex on Mon Nov 18, 2002 at 04:03:37 AM EST

You say that many people don't know how to work with threads. I would deal with this by writing an article that explains to them how to do it properly, instead of saying "Go away! Threads are not for you! How could you ever dare to play with the toys of the big boys?"

The people who don't know wheter their program needs threads should really learn how to find out, instead of just abandoning the idea. Many problems are a lot easier to solve with threads and you don't even need a single lock or semaphore. For example, a web filtering proxy that spawns a proxy object in its own thread for every connection the client makes to a web server. Every proxy object maintains its own state and is destroyed when the connection is closed, or maybe reset to be reused. Any newbie can do that (when instructed properly), whereas it would actually be a lot harer to get the same functionality without threads.

Furthermore, the actualy problem ofetn lies somewhere else. For example, you can't write multithreaded code in an OO language if you don't know the difference between static and instance variables. But you actually can't do much sensible at all in an OO language if you don't know the difference between static and instance variables. Threads are not the real problem.

[ Parent ]

Ummm....No (4.25 / 4) (#43)
by moshez on Mon Nov 18, 2002 at 04:43:16 AM EST

In a different universe, I might have been writing an article titled "Assembly Programming Considered Harmful". Thank the good lord, we live in a universe in which most programmers have realized Assembly Programming is too difficult. That is, it is difficult to learn, and even after learning, it is difficult to use correctly, and after using incorrectly it is difficult to debug. This is not to say people should never ever use assembly programming -- merely that they should strive to avoid them. I merely try to point out that the same reasons apply for threads.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
not inherently difficult (1.00 / 1) (#50)
by nex on Mon Nov 18, 2002 at 04:58:36 AM EST

Well, this comparison isn't perfect in all areas, since threads and assembly programming are such orthogonal concepts that they don't compare very well. However, in one are the comparison is okay: Both with Assembly programming and with trheads you can do simple and hard things. A rather unexperienced programmer might want to write just the innermost loop of his ray-tracer in Assembler to see if it will get faster, and even succeed at it, while even the most experienced programmer would hardly ever want to write a whole OS in pure Assembler. Likewise, there are uses of Threads that are extremely difficult to understand and implement indeed, but OTOH, there are cases in which even a newbie can make good use of threads. Threads by themselves are a rather simple concept, they aren't inherently hard to program. The hard part is understanding the dangers of deadlocks etc., which you also have to do in single-threaded programs. Two users on the same machine might be using your single-trheaded program at the same time, writign to the same config file. You have to care about that as well.

Of course people should avoid doing something they don't understand. But not doing anything is not the best solution. Lerning to understand some things and then doing them often makes sense.

[ Parent ]

No! (4.33 / 6) (#53)
by joto on Mon Nov 18, 2002 at 05:02:07 AM EST

For example, a web filtering proxy that spawns a proxy object in its own thread for every connection the client makes to a web server. Every proxy object maintains its own state and is destroyed when the connection is closed, or maybe reset to be reused.

Or it could fork() a new process. Which is just as simple, but safer.

To explain this, let's put on the maintenance-programmers hat for a minute. You are handed the web-filtering proxy program written by somebody else who left the company some years ago. Your task is to add some feature, update for new protocols, or whatever...

If it uses threads, then you have to make sure that it only accesses memory in safe ways before you apply a change. If it uses processes, that is pretty much guaranteed, and it saves you a lot of work going through the program to make sure it is correctly designed.

Any newbie can do that (when instructed properly), whereas it would actually be a lot harer to get the same functionality without threads.

The fact that any newbie can do it, doesn't mean that any newbie should do it. Any newbie can do pretty much any task in horrible ways that happen to work when instructed properly.

My experience with teaching newbies tell me that they like...

  • reusing global variables to save space, and to save writing all those declarations
  • putting as much as possible into one function to avoid dealing with more of those nasty declarations
  • using "variants" in VB to avoid those bugs that pop up when they do not understand the difference between 0 and "0" (and of course, to make it easier to reuse them for a different purpose, if they are global)
  • pictures of swimsuit babes in their gui more than working code
  • copying code they do not understand instead of actually thinking about the problem
  • solutions that work "most of the time", but is simpler to "understand"
  • putting everything into one file to make it easier to compile
  • hundreds of intermediary variables instead of a simple well-written formula
  • state, not referential transparity
  • magic, not science

And if you can move them away from bad habits instead of teaching them how they can be made to work, that is the right approach. And fork() is in fact easier than pthread_create().

[ Parent ]

-1, too C++ centric ;-) (2.80 / 5) (#65)
by nex on Mon Nov 18, 2002 at 05:24:23 AM EST

I didn't intend to make any difference between threads and processes in my comment above, as it doesn't make any difference in the context of the arguments presented by the article, namely "Concurrency is hard to understand" etc.

If you use processes, they are assigned seperate memory spaces, sure, but that doesn't help at all to avoid deadlocks or inconsistencies, because if to threads have to share a certain piece of information, they still have to share it if they are seperate processes.

When I wrote about a simple web filtering proxy that any newbie could write, I was of yourse thinking of Java, where it is much easier to spawn a new thread than to fork a new process.

> Any newbie can do pretty much any task in horrible ways that happen
> to work when instructed properly.
If it is horrible, he was not instructed properly. I didn't say a newbie should use threads whereever he can. I said you shouldn't forbid him to try it in situation in which it actually makes sense.

[ Parent ]

C++ centric? (5.00 / 4) (#71)
by joto on Mon Nov 18, 2002 at 05:49:24 AM EST

My examples of newbie-attractors were all from an introductory course in VB. Fork() is not C++ centric, it is unix-centric. On windows, you have spawn(). It can be used for much the same purpose, although not as conveniently.

I was of yourse thinking of Java, where it is much easier to spawn a new thread than to fork a new process.

Somehow this obvious fact passed by me. Now, who is language-centric?

But yes, in java, threads might be considered more reasonable. But is this because they really are, or because java makes (1) processes extremely heavyweight (requiring a full JVM) (2) passing file-descriptors impossible without native code (3) non-blocking I/O available only in the latest versions?

Realistically, I would tend to put the newbie in the direction of threads here too, but only because it is Java.

[ Parent ]

sorry, my comment was unclear (2.66 / 3) (#75)
by nex on Mon Nov 18, 2002 at 06:07:05 AM EST

> My examples of newbie-attractors were all from an introductory course in VB.
I didn't respond to those. These are really good examples of silly things many beginners like to do. However, it's the fault of their teacher that they don't know that and why these things are silly and these examples aren't about things that are too hard to be understood by beginners.

> Fork() is not C++ centric, it is unix-centric.
Correct. I was just thinking that pthread_create() was from C(++).

[ I was thinking of Java. ]
> Somehow this obvious fact passed by me. Now, who is language-centric?
Well, the fact wasn't obvious, I didn't mention any language. I just gave an example for my argument,
>> Many problems are a lot easier to solve with threads and you
>> don't even need a single lock or semaphore.
One of these problems might be a filtering proxy in Java, while a filtering proxy in C++ could be a whole different story. The point is that sometimes threads are in fact a reasonable thing to do and not hard to implement at all. I didn't mean to be language-centric, hence I didn't mention any specific language. Of course you are right in saying that you should carefully choose between processes and threads. But as I said, the article is about not having any concurrent paths of execution at all. No multiple threads, but no multiple processes as well, but instead something like an event based model. Your take on processes is correct, but off topic.

[ Parent ]

Your Comment Was Clear (4.00 / 4) (#76)
by moshez on Mon Nov 18, 2002 at 06:11:48 AM EST

Your reasoning is wrong. You proved why Java is a bad language: because it forces programmers to use threads where processes would be a better choice.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Was it ? (3.25 / 4) (#80)
by bugmaster on Mon Nov 18, 2002 at 06:28:18 AM EST

I think his comment shows that C++ is a bad language, because it forces you to use processes where threads would be a better choice :-)

Anyway, the above paragraph is sarcastic. I don't think you can just claim that "processes are a better choice so language X sucks" without ample justification.

While we are on the subject, Java seems to bypass many problems with threads that you mentioned in your article. For example, it is very difficult to create a deadlock in Java (compared to C/C++/whatever). It is also very easy to isolate critical sections: if you are an OOP programmer, your entire program is broken up into methods already. It is also easy to make sure that the target thread (which is its own object as well) gets only as much access to outside data as it needs to. This is actually better and clearer than Unix pipes.
>|<*:=
[ Parent ]

A Deadlock in Java (5.00 / 2) (#83)
by moshez on Mon Nov 18, 2002 at 06:33:04 AM EST

Would be just as easy -- as soon as you used any non-trivial program. Java avoids many deadlocks by using locks which are fairly fine-grained (per-method), and which therefore incur a significant penalty. And which don't solve the non-trivial problems with threads, so you still have to do manual locking if you're using threads in a way which any other language would have let you used processes for.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
one-banana problems (4.00 / 4) (#100)
by nex on Mon Nov 18, 2002 at 07:59:34 AM EST

Firstly, I'd like to say that I couldn't agree more with bugmaster's posting above. Your argumentation, along the lines of "Your reasoning is wrong because I don't like threads and desperately want to be right" just plain sucks.

Secondly:
> A Deadlock in Java would be just as easy.
Well, of course it's easy to program a deadlock in Java. You can easily program a dealock in just about any language, you don't have to be a genius to achieve this. But it is also rather easy to write code that can't possibly ever get into a deadlock. Depending on what exactly you're doing and what libraries you use, you can usually get away with a good design to avoid all synchronisation problem, you don't have to manually implement a single semaphore.

Thirdly: The only thing a seperate process in Java would buy you would be a second set of all the static members and methods. However, if you find you need two instances of something static, the design is faulty and it should actually be dynamic. Furthermore, in Java you never run into problems with shared memory space, because you never see any memory addresses anyway. There are no pointers in Java. The virtual machine takes care of all this. You'll also never see a thread crash and take the whole process down with it, because exception handling will prevent this.

Of course the price you pay for such convenience is performance. But Java has no disadvantage there, it's just a different approach, which is smarter in most cases anyway. C++ wants you to re-invent the wheel all the time. It is fast per default, but provides no convenience. Since you can't work productively with pure C++, you use all kinds of class libraries, e.g. smart pointers for garbage collection. However, these introduce the same performance penalties as the Java counterparts. Where performance becomes critical, you have to do manual fine-tuning. Java has some of those conveniences per default, but it is also open to manual fine-tuning. You can always write smarter code with less multi-threading problems, and in rare cases in which even that doesn't cut it, you can still assemble byte-code yourself, or write native code, if appropriate.

Oh, and, just out of curiosity: What would be a good example for a "non-trivial problem with threads"?

[ Parent ]

java locking granularity (4.00 / 1) (#137)
by macpeep on Mon Nov 18, 2002 at 11:36:09 AM EST

Actually Java allows even more fine-grained locks than per method. Every object is also a mutex and you can wrap any piece of code in a synchroznied(obj) { } block and have that synchronized using the mutex in the object that you specified.

[ Parent ]
Maintenance (3.50 / 4) (#101)
by Lacero on Mon Nov 18, 2002 at 07:59:53 AM EST

To explain this, let's put on the maintenance-programmers hat for a minute. You are handed the web-filtering proxy program written by somebody else who left the company some years ago. Your task is to add some feature, update for new protocols, or whatever... If it uses threads, then you have to make sure that it only accesses memory in safe ways before you apply a change. If it uses processes, that is pretty much guaranteed, and it saves you a lot of work going through the program to make sure it is correctly designed.
I've seen this mentioned a lot in this story, and I'm going to take issue with it here.

Maintenance programmers shouldn't have to go through the program to make sure it's correctly designed. They should be able to look at the spec, look at the docs the original programmer wrote, and know exactly how it all works.

You seem to be saying we should throw away complex programming techniques because reading them without documention is hard. Understanding any large program without documentation is hard. And yes some flow control choices are easier than others, but the issue here is proper design and documentation.

I realise I'm living in a fantasy world but it's just as unrealistic as no one using threads again :)

[ Parent ]

Re: Maintenance (4.20 / 5) (#103)
by joto on Mon Nov 18, 2002 at 08:26:55 AM EST

Maintenance programmers shouldn't have to go through the program to make sure it's correctly designed. They should be able to look at the spec, look at the docs the original programmer wrote, and know exactly how it all works.

Yes, I agree that you live in a fantasy world. In my day-job, I manage code where people use up 13 (I've counted) levels of intendation, have produced (I guess) about 30k-loc code in order to have an OO framework for something that really takes about 15 lines if done simple, and the code consists of about 80% comments. None of that is what you want.

You seem to be saying we should throw away complex programming techniques because reading them without documention is hard.

No. I'm saying that we should favour the right solution instead of documenting the wrong.

Understanding any large program without documentation is hard.

Yes, but when you have the choice between documentation and clarity, you should always prefer clarity.

And yes some flow control choices are easier than others, but the issue here is proper design and documentation.

And avoiding threads is one clear design criterion, just as avoiding goto is. Documenting them is no excuse, if they are not needed (although, sometimes they are, which is why most languages have both).

[ Parent ]

Re: Maintenance (3.00 / 3) (#119)
by Lacero on Mon Nov 18, 2002 at 10:04:28 AM EST

Firstly the specific example you gave back a ways I agreed with. But as I seem to have got myself involved in this...

Yes, but when you have the choice between documentation and clarity, you should always prefer clarity.

Yes, but the program always has to work. In some cases this means the tiny speed increase is necessay.

And avoiding threads is one clear design criterion, just as avoiding goto is. Documenting them is no excuse, if they are not needed (although, sometimes they are, which is why most languages have both).

Of course I agree with the second sentence. But not the first.

A lot of the time threads are unneeded, but I don't believe we should explicitly avoid them. The only advantage processes have over threads is that all potential memory deadlocks are flagged as shared memory. It's still just as easy to get caught in problems, you just know you can't access the private memory of the other thread.

A good design should stop you accessing other things private memory anyway. In C you don't use global variables and keep your pointers pointing somewhere safe. Same in C++ except it's easier because you have object encapsulation.

I think the notion of data hiding has come along enough that this works fine.

[ Parent ]

Re: Re: Maintenance (4.00 / 1) (#216)
by joto on Mon Nov 18, 2002 at 07:10:00 PM EST

Yes, but the program always has to work. In some cases this means the tiny speed increase is necessay.

Bah! Do you really think I am in favour of stuff that doesn't work but looks pretty..?

The only advantage processes have over threads is that all potential memory deadlocks are flagged as shared memory.

Huh. Deadlocks flagged as shared memory? You make no sense. The advantage of process is that you can be reasonable shure that they do what you expect, because there is a reasonable level of correspondence between testing and real-world behaviour. This is not at all true for threads.

Furthermore, processes avoids the problem of thread-safety, which is often difficult in OSes not specifically written for threads. And lastly, but maybe most important, processes make you state explicitly when something is shared, which means it does the right thing, in just the same way that global variables do the wrong thing.

A good design should stop you accessing other things private memory anyway.

Yes, and if we both agree that this is good design, I fail to understand why you must insist on relying only on good design, and not having the OS help you enforce it.

I think the notion of data hiding has come along enough that this works fine.

It certainly has, but that is no reason why we should rely on data-hiding techniques only, when we can have the OS enforce a sane policy on us, effectively guaranteeing that each communication between the processes will be easily grepable.

[ Parent ]

Data hiding (2.00 / 1) (#294)
by Lacero on Tue Nov 19, 2002 at 06:13:58 AM EST

Bah! Do you really think I am in favour of stuff that doesn't work but looks pretty..?

No.

Furthermore, processes avoids the problem of thread-safety, which is often difficult in OSes not specifically written for threads.

Well writing threads in an OS that doesn't support them is always going to be more difficult.

And lastly, but maybe most important, processes make you state explicitly when something is shared, which means it does the right thing, in just the same way that global variables do the wrong thing.

This is what I meant by saying all deadlocks are flagged as shared memory. You can't deadlock unless you're accessing an area of shared memory, and all such cases are easily seen. Using OO all such cases would be recognisable as the data class would inerit from a Shared class, although I recognise it's not a hard rule like you are arguing for.

It certainly has, but that is no reason why we should rely on data-hiding techniques only, when we can have the OS enforce a sane policy on us, effectively guaranteeing that each communication between the processes will be easily grepable.

Yes. But bogging the system using processes instead of threads comes from the same midset that puts global locks round every data access "just in case".

[ Parent ]

Sweet, fork bomb (1.50 / 2) (#156)
by ma luen on Mon Nov 18, 2002 at 12:16:58 PM EST

What you suggest for the filtering proxy is a slight variation on the classic fork bomb. Although it shouldn't have the exponential growth the nature and number of the new processes should wad up the scheduling queue so that effectively no other processes have a chance to run. Thus achieving a similar effect.

[ Parent ]
You may know threads... (5.00 / 3) (#32)
by StephenThompson on Mon Nov 18, 2002 at 04:27:00 AM EST

But do you know large projects with many developers? It only takes one programmer who thinks he knows what hes doing in a large code base (who doesnt) to take a flagship crashing to the ground, always of course being missed by the QA team because of the timing related nature of deadlocks or memory trashing. Design SIMPLE. KEEP IT SIMPLE.

[ Parent ]
Well... (4.80 / 5) (#34)
by joto on Mon Nov 18, 2002 at 04:31:04 AM EST

I am not the author, however, I agree fully with him, and I actually do know something about multithreaded programming.

The pitfalls of multi-threaded programming are well understood both in theory and in practice. Using semaphores and locks to secure critical sections is a simple and elegant solution.

Yes, the pitfalls are well understood. However, how to avoid them is not. That's why threads should be avoided as much as possible.

The only danger is abuse from amateur programmers.

That can be said of any programming construct. But of course, what is an amateur programmer? Is it someone not paid professionally? Is it someone who knows less than me about [insert favourite subject here]? Is it someone who is not Edsger Dijkstra? The only way to be really shure you avoid bugs in multithreaded programs is through formal verification, and last time I checked, that was not a topic most "professional" programmers knew very well.

Furthermore, there are many situations where a mutli-threaded program is the best solution.

No, not really many. There are however a few, where multithreaded programming really is needed. But very rarely does those tasks show up in the average programmers task-list.

There are some exceptions to this rule. The win32 api, and java gui programming pretty much forces you to use threads. That doesn't mean they are the best solution, only that your vendor forces you in this direction.

When it comes to data- or processor-intencive tasks, threads are often not the wonderful solution people think it is (think processes, shared memory). The main reason is that you can't test threads separately, and be somewhat sure that when put together they will actually work. With processes, on the other hand, you do have that nice cosy feeling. And, if you for some strange reason find out you need threads anyway, it's much easier to convert two separate programs into one, than it is to convert a hodgepodge of threads and locks into separate processes.

"Multiple processes" is the exact same thing as threads except now you are burdened with using semaphores or pipes to communicate, in the linux kernel there is no entity that corresponds to a thread, all threads are processes.

Forgive me for laughing, but you claim that those programmers who cannot deal with threads properly are amateur programmers, but somehow you find semaphores or pipes to difficult to handle? Using pipes, semaphores, or shared memory is no different than using the multithreaded equivalents of worker-consumer-buffers, locks, or plain memory access. On the other hand, in the latter case, you loose debugability.

What is the difference between calling a function which retrieves data from another process and calling a lock function?

Debugability. Independent testing. Thread-safety.

Co-operative multitasking is and always will be a disaster for anything but simple or embedded applications.

Maybe, but most things are simple, and shouldn't have to deal with the complexities of threads. But in most cases I would prefer an explicit state-machine (as in event-driven programming), or separate processes, to cooperative threads, but that could just as well be because my programming environment make that easier.

Event based programs are the best solution you mention, but simply loony for most applications, they force you to structure your program in a cumbersome way. Event models are useful in programs that must deal with a variety of disparate tasks in real time (videogames, toolkits, word processors, communications systems), you'll notice they aren't widely used outside of this.

I agree that event-driven programs are often unvieldy, especially when they become large. And if you can separate the logic to do some separate task outside the event-loop, that is often better. But then it can just as well be done with processes as with threads.

By the way, network programming is a very good match for event-driven programming.

Your conclusion is faulty as well. It is true that threaded programming requires more care and more forethought, but this is engineering my friend, problems are not guaranteed to be easy to solve, fun, or to be described by any other positive adjective.

Agreed. Sometimes you have to put in the extra work to make a proper solution and design. While it seems easy enough to hack up a few extra threads, this is no excuse for not having a proper architecture and design. And while it is possible to do so using threads, usually processes are easier.

Billions of threaded programs are running on computers everywhere as we speak, proof enough that the concept works and it works fine.

Yes, and billions of programs using mostly goto was running on computers everywhere as Dijkstra wrote his paper. Proof enough that the goto concept works and it works fine. This proves nothing. If you want to "prove" that threads are the better solution, you have to take into account the whole lifecycle of the program. That includes not only initial design and coding, but also the maintenance programming that will go on for maybe decades afterwards. And in that case, it is better to have something that is carefully crafted to be easy to understand, where parts are easily replaceable, and can be debugged and understood separately, than a carefully crafted multithreaded program that needs to be understood as a whole, before you can apply changes to it.

Even worse then that, this is a simple problem with an elegant and simple solution;

On the contrary, multithreaded programming is a complex problem that continue to baffle computer scientists with its complexity. And while there exist some "simple solutions", such as monitors, they are not something you are likely to find in your favourite systems programming language (neither do they solve all problems). By claiming that multithreaded programming is easy, it is you who come out as the uninformed amateur programmer.

which leads me to believe you are either uniformed.

Or simply right.

[ Parent ]

on the contrary my good fellow (3.00 / 5) (#48)
by omegadan on Mon Nov 18, 2002 at 04:56:53 AM EST

On the contrary, multithreaded programming is a complex problem that continue to baffle computer scientists with its complexity

I agree with most of your agreeing with me :D but on this point we will have to disagree. There is exactly *1* rule of multithreaded programming, and its not baffling *anyone*. That rule is:

Use a lock on *any* object that is used by another thread.

That's *IT*! That is the *whole* of threaded programming. No mystery at all. That is my real problem with moshez article, he is presenting threaded programming as a black art, when it is about the most simple thing you could imagine.

Religion is a gateway psychosis. - Dave Foley
[ Parent ]

Boy, This is a Recipe for Bugs (5.00 / 2) (#59)
by moshez on Mon Nov 18, 2002 at 05:11:38 AM EST

No, the rule you gave (while important) is not the only rule. Using this rule you will encounter exactly the dead-lock problems I explained above if you do not maintain strict order of locking. If a line of code modifies two objects, you need to get both locks. Everywhere that happens, you must get those locks exactly the same order. I leave it to your consideration whether this breaks modularity or not.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
on the subject of deadlocks (2.20 / 5) (#72)
by omegadan on Mon Nov 18, 2002 at 05:54:57 AM EST

agreed, but deadlocks are simply a detail of locking. I didn't mention it because, frankly I wasn't thinking about it. If someone has learned how to acquire a lock then theres no reason to believe they haven't learned how to acquire multiple locks. You seem to be taking the position that someone who knows nothing about threads should be able to use them easily. Which is my main point really, threads are only dangerous in the hands of amateurs.

Religion is a gateway psychosis. - Dave Foley
[ Parent ]

Ahh, you weren't thinking about it... (5.00 / 4) (#74)
by joto on Mon Nov 18, 2002 at 06:03:36 AM EST

agreed, but deadlocks are simply a detail of locking. I didn't mention it because, frankly I wasn't thinking about it.

Would you care to show me one bug that was not the result of "not thinking about it". The fact is that there is much more to think about when using threads. So much more in fact, that it should be avoided whenever possible.

[ Parent ]

mixed analogy ... (2.20 / 5) (#78)
by omegadan on Mon Nov 18, 2002 at 06:24:27 AM EST

yea, thats *definatley* how most bugs come into being ... but its not what I meant. What I meant was, I wasn't thinking about it because anyone who knows about locks, should know how to avoid deadlocks. Complementary knowledge.

Religion is a gateway psychosis. - Dave Foley
[ Parent ]

simple? (5.00 / 5) (#67)
by joto on Mon Nov 18, 2002 at 05:25:13 AM EST

There is exactly *1* rule of multithreaded programming, and its not baffling *anyone*. That rule is:

Use a lock on *any* object that is used by another thread.

Unfortunately, it is not correct. By doing that, you will easily introduce something just as dangerous as a race condition. It is called a deadlock, and is also something most programmers happen to know about. How to avoid deadlock is just as important as how to avoid race-conditions.

But there are other issues in multithreaded programs as well. There is the issue of thread-safety (many OS functions are not). There is the issue of signal handling (not at all trivial under pthreads), albeit not entirely easiy in single-threaded code either. But, ok, so much for the "science" of multithreaded programming.

Usually, you do multithreaded programming for a purpose, and that purpose is speed, whether perceived (as in a GUI doing background tasks), or real, as in computationally intensive tasks. And that is where the "art" comes in.

Sure, you can put locks around everything. That means you will end up with an almost single-threaded program. You need to restructure your program and data-structures to minimize sharing. You need to deal with not just race-conditions and deadlocks, but fairness, starvation, and a number of other issues.

To do this, you need to think about how the individual tasks communicate, which tasks they should do, and how to combine them together in a whole. It's not easy, and the "temptations" of simply being able to access global memory, make it a lot more easy to shoot yourself in the foot.

Processes give you safety by default, and communication is a hassle, whereas threads give you communication by default, and safety is a hassle. It can be compared to structured programming with lots of functions and procedures taking parameters, or unstructured with lots of gotos and global parameters. Unstructured is easier at first, but you are bound to shoot yourself in the foot sometime.

So the ideal multithreaded program is one that can be rewritten with processes. And if you can restructure your ideal program with processes instead of threads, you should do so, for the sake of clarity. In most cases, it is possible. There do, however remain things that are better done with threads, just as there do remain things that are better done with goto. But they are rare, and if you happen to stumble over one, chances are you'll already be knowledgeable enough to handle it.

[ Parent ]

Not speed, scalability (none / 0) (#282)
by StephenThompson on Tue Nov 19, 2002 at 02:58:52 AM EST

Threads do not speed anything up, in fact they add a load to the system.

Threads are used to allow scaling and fine grain performance.  That is to say, they allow bigger resource pools with better latency.

[ Parent ]

Depends (none / 0) (#284)
by moshez on Tue Nov 19, 2002 at 03:04:09 AM EST

It seems you're using threads to compensate for bad design. Most of the examples you gave could be better solved with other techniques. The problem is that threads are so alluring because they let you get away from these design problems -- making you pay in spades later.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
"One rule" (4.00 / 1) (#235)
by sigwinch on Mon Nov 18, 2002 at 09:06:05 PM EST

There is exactly *1* rule of multithreaded programming, and its not baffling *anyone*. That rule is:
 
Use a lock on *any* object that is used by another thread.
*cough* cache ping-pong *cough*

--
I don't want the world, I just want your half.
[ Parent ]

Abuse from amateur programmers (4.66 / 3) (#130)
by wedman on Mon Nov 18, 2002 at 11:14:06 AM EST

Damn them dangerous amature programmers! Someone take their compilers away! :P

~
DELETE FROM comments WHERE uid=9524;
[ Parent ]
Multithreading is good for high-performance (3.00 / 3) (#23)
by Quila on Mon Nov 18, 2002 at 03:56:30 AM EST

You then get to take advantage of SMP and Intel's Hyperthreading. Without multithreading, you won't see much of a gain with these technologies.

Wrong (3.50 / 2) (#24)
by moshez on Mon Nov 18, 2002 at 03:59:26 AM EST

As I said below, you can often get the benefits of SMP from restructuring your program as several co-operating processes. This, of course, works optimally if the needs for communication between the processes are small. However, if they are not, implementing it as a multi-threaded apps will often waste much of your precious CPU time doing non-productive things like locking and unlocking.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Basic lock implementation (3.66 / 3) (#41)
by i on Mon Nov 18, 2002 at 04:41:27 AM EST

is about 15 instructions on a Pentium. How much is an interprocess communication primitive of your choice?

and we have a contradicton according to our assumptions and the factor theorem

[ Parent ]
Hopefully (3.33 / 3) (#47)
by moshez on Mon Nov 18, 2002 at 04:52:55 AM EST

Your processes do not need to communicate enough that the communication itself is a bottleneck.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
but (4.00 / 1) (#228)
by emmons on Mon Nov 18, 2002 at 07:43:26 PM EST

What if they do? 15 opcodes vs. a context switch for ICP is quite significant. Even if you only need to do it every thousand lines of code, the syscall is still a *lot* of unnecessary overhead.

---
In the beginning the universe was created. This has made a lot of people angry and been widely regarded as a bad move.
-Douglas Adams

[ Parent ]
Why? (3.00 / 2) (#118)
by Quila on Mon Nov 18, 2002 at 09:44:26 AM EST

Why make the OS take the overhead to juggle two separate processes when you can just use standard locks and multithread? You're right that threads are best not used in practice by amateurs, but there's no reason not to thread if you need it when it's done properly.

[ Parent ]
Try to make sense, please! (none / 0) (#421)
by eduar09 on Sun Nov 24, 2002 at 01:37:28 AM EST

Has it ever occured to you that threads can also have very little communication between them? And share very little data?

Just because I'm using threads does NOT mean I have to operate on the same data in my threads. Thread A operates on Object A.. Thread B operates on Object B.

Granted, this isn't the case all the time, but when you promote co-operating processes as though it has an advantage over threads becasue of locking somehow.

If I have implement something as co-operating processes it will need have locks in the same places the implementation as threads needs to have locks. No MORE. No LESS.

[ Parent ]

Threads vs Processes (2.75 / 4) (#33)
by bugmaster on Mon Nov 18, 2002 at 04:28:01 AM EST

If you have some processes that need to share information, how are they better than threads ? Whenever information needs to be shared, you get into race conditions as described above. Yes, it might be the case that putting locks in the right places is hard, but you need to do it for processes too, in one form or another. Otherwise, you get race conditions like the ones you described above.

In college CS courses, they usually teach you how to acquire/release locks; how to avoid deadlocking (dining philosophers), and how to boost performance (by not putting one global lock around your program, for example). These issues are harder to grasp than "10 print "hello"; 20 goto 10", but they are not insurmountable.
>|<*:=

Answer (4.25 / 4) (#46)
by moshez on Mon Nov 18, 2002 at 04:50:22 AM EST

Data in threads is shared by default. Data in processes is private by default. Quoting Frost, "and that makes all the difference."

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Fair enough, and I agree (4.25 / 4) (#52)
by epepke on Mon Nov 18, 2002 at 05:01:06 AM EST

However, there are still a lot of people who do programming who have not had the benefit of a CS education.

I used to work with a programmer who was great, very meticulous and productive. I would recommend him wholeheartedly for any programming position. However, I found out one day that he did not know what a state machine was. This was mind-boggling to me.

I just don't see how you can grok computing without state machines or grok databases without set theory or write efficient code without at least a basic understanding of complexity proofs. Yet many seem to do reasonably well.

I think that, during the dot com days, not only did a lot of people who didn't know the basic math get into programming, but there was a kind of perverse anti-intellectualism. Don't need no damn employee who knows no damn finite math, just want someone who gets the job done! Sorry about the rant, but I think that there are a lot of people in the business who really don't have the logical and mathematical background. Many of them can do almost anything, but safe threads are perhaps not one of these things. Not that there's anything wrong with wunderkinder, but maybe some remedial CS math vocational courses would be appropriate.

Truly, these things are not hard with a little bit of decent graph theory, but that isn't a given. Furthermore, even if you have the theory, there's always the chance that there's someone else on the team who does not. Even if your code is tighter than a rat's ass, somebody else might mess it up.


The truth may be out there, but lies are inside your head.--Terry Pratchett


[ Parent ]
Education (none / 0) (#61)
by bugmaster on Mon Nov 18, 2002 at 05:15:33 AM EST

Sorry, I did not mean to imply that a formal education is the only way to learn about threads -- smart people can usually figure things out on their own.

I was merely trying to point out that "most people haven't learned how to use threads so threads are bad" is not much of an argument. It's like saying "no one should drive because driving is hard". Yes, it's hard, but in some cases that's the only way to get from point A to point B.
>|<*:=
[ Parent ]

My Point Was (4.75 / 4) (#63)
by moshez on Mon Nov 18, 2002 at 05:21:01 AM EST

That unless you both know a lot and you are incredibly careful, using threads will lead to bugs which will not not educate you on the things you need to learn to not repeat them, but rather induce you to a kind of a work-around dance and unmaintainable code -- and that there are better alternatives to threads, which work almost as well for most purposes, and are significantly easier to use, and significantly harder to misuse.

My article was geared to someone who thinks "handling more than one thing" means "threads", and thinks that threads are some kind of magic formula which makes everything not block while requiring no more thought than "protect shared data with locks".

I thought it important to show the problems with threads, and show how the alternatives solve them.



[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Maybe I wasn't clear (5.00 / 2) (#70)
by epepke on Mon Nov 18, 2002 at 05:41:12 AM EST

My point was that maybe threading is one of the minority problems in computing that really does benefit from an education, formal or otherwise, in the basic logic and math of CS. At best, it's very difficult to figure out using trial and error. You can figure it out well enough to make it work most of the time with trial and error, but that isn't good enough. However, if you're trained or have learned to deal with graph theory, you can fairly easily "feel" when a situation might lead to a deadlock. (I use "feel" somewhat literally; my internal experience with such things involves imaginary tactile sensations. Deadlock situations feel "crunchy" to me. Other people might "see" it.) It's much harder to get a gestalt from watching programs break.

Same thing with state machines and other automata. There are a lot of good ad hoc parsing techniques that everyone stumbles onto eventually, but there's nothing like knowing what a pushdown automaton does and what kind of languages it's mathematically equivalent to in order to see the right solution to some problems instantly. It doesn't mean that you always have to implement a pushdown automaton or an LALR1 parser, but at least you have an idea of what to do.


The truth may be out there, but lies are inside your head.--Terry Pratchett


[ Parent ]
Re: Maybe I wasn't clear (4.66 / 3) (#84)
by bugmaster on Mon Nov 18, 2002 at 06:37:21 AM EST

My point was that maybe threading is one of the minority problems in computing that really does benefit from an education, formal or otherwise, in the basic logic and math of CS.
I disagree. I think all problems in CS, and in other disciplines, really do benefit from an education, formal or otherwise. For example, consider driving a car -- a very difficult process by human standards. You can simply memorize the rule "look in your mirror before making a turn", and you'll be fine for a while. However, at some point a car will "come out of nowhere" and blindside you, turning your shiny new Porshe into crumpled tin foil. If you take the time to learn about blind spots, braking distances, etc., you will become a much better driver -- almost as a side-effect.

The same can be said about the Big-O notation (bubble sort is worse than quicksort... or is it ?), functions (why not just put everything into main ?), recursion (how do I implement fibonacci numbers ? There are at least two ways), cooking (who cares if you preheat the oven or not ?) etc. If you take time to understand the underlying issues of any problem, you will be much better at solving such problems.

Yes, it's true that some issues are easier to grasp than others, but without proper understanding all issues will appear hard.

Oh, and for the record, deadlock feels "sticky" to me, not crunchy :-)
>|<*:=
[ Parent ]

Hmm (4.00 / 1) (#142)
by tzanger on Mon Nov 18, 2002 at 11:44:28 AM EST

I used to work with a programmer who was great, very meticulous and productive. I would recommend him wholeheartedly for any programming position. However, I found out one day that he did not know what a state machine was. This was mind-boggling to me.

/me raises his hand

I cannot read state machine diagrams. I know what they are but can't read the diagrams. I don't know set theory but am a pretty decent database designer. I don't have any formal knowledge of complexity proofs, either.

I'm not saying any of this makes me some kind of superhero, but what I am trying to say is that you don't need to have the formal education to do well in a subject. It does help, though.

Where it helps the best is in the "head-wall repetitive-collision avoidance" department: if you've got the formal education you've learned what happens when you do things the wrong way. Us noneducated folk learn it the hard way (out in the field). I'm not saying formal education is better, per se, only because I've seen a lot of highly educated people beat their heads against the wall with the same problems I have -- they forgot what they learned.

The only advantage I can see of learning the hard way is that the lessons are a lot harder to forget than if you studied them in school. :-)



[ Parent ]
Heh (5.00 / 3) (#182)
by ubu on Mon Nov 18, 2002 at 04:07:47 PM EST

The reason for the anti-intellectualism was that for every 1 person with knowledge of advanced theory and the ability to use it properly, there were 50 people with the degree but no apparent ability whatsoever. And unfortunately, along with the degree and the cluelessness came a mind-exploding arrogance and sense of entitlement that drove many brilliant and knowledgeable (but self-taught) professionals to the brink of homocide.

I have known a great many people who fall somewhere in the middle: excellent theory and an unmistakeable ability to reason, yet with no practical, intuitive grasp of design issues. What the inveterate, intuitive designer immediate discards as a poor basis for the project, this guy will grapple with until someone invokes the magic phrase that takes him back to a CS 5534 lecture years ago and reminds him what he's dealing with.

Experience and an inquisitive mind, coupled together, will beat out the generic CS-educated professional every goddamned time. I mean, for crying out loud, what does it really take to educate oneself in Finite Automata theory, in this day and age?

And he's less likely to have weird sexual fetishes, in my experience.

Ubu


--
As good old software hats say - "You are in very safe hands, if you are using CVS !!!"
[ Parent ]
Hey! (5.00 / 1) (#201)
by epepke on Mon Nov 18, 2002 at 05:28:47 PM EST

Well, I've got you and bugmaster trying to disagree with me in opposite directions, which is a good heuristic sign that I'm on to something right.

I have known a great many people who fall somewhere in the middle: excellent theory and an unmistakeable ability to reason, yet with no practical, intuitive grasp of design issues.

Design is something that I think is poorly taught (if at all) in CS courses. Sometimes I wonder if there is a good way to teach it and, if so, what it would look like. I used to get into arguments with people all the time over this. It's much like science: the basic principles of science aren't really taught; there's just a hope that people will pick them up by osmosis. Unfortunately, for 20 years or so it's been possible to get a CS degree without ever having been responsible for a large program, and I think that it takes a few of those to get an intuitive feel down pat.

In my experience, I did programming for a number of years before I set foot in a CS class. When I took the CS classes, at first, I chomped at the bit quite a lot. However, now 20 years later, I'm really glad I went through that experience.

Since last night, I've been thinking about what a crash, vo-tech-type course on the basics of CS might look like and whether it would be beneficial and/or popular. The problem isn't, as you say, educating oneself in Finite Automata theory, but rather more in knowing what it is and why it's relevant in the first place.

I think I'd base everything on practical examples. One of the problems that an absense of an understanding of state machines that I've seen are unweildy and ad hoc event-handling routines. So, I think I'd start off with a simple, text, interactive loop program and add "customer demands" to it. Most programmers, in my experience, will start off with ad hoc if blocks, which works fine at first but usually gets out of control, resulting in bugs and strange behavior. Then I'd show how a state machine provided a cleaner framework that naturally avoided the bugs.

Way back when I learned about finite automata, my first thought was, "Man, you could build pinball machines with this stuff!" At the time, nearly all pinball machines were electromechanical, with the logic provided by relays and wires. They were, however, fairly sophisticated in their logic by this time, with goals and subgoals, much more complex than the ring-a-bell-and advance-the-counter machines of the 1950's. I told my father about this. He knew nothing about computers, but he worked in a pinball arcade and showed me a schematic for one of the machines. It was obvious from them that stuff was just tacked on. I played around with some FSA machines and generated relay networks and got a cleaner and more robust and fault-tolerant design. The real design was quite beautiful in an American engineer-by-the-seat-of-your-pants sort of way, but a design with some theory in it did work better.

I still think that automata are a fine way to specify pinball machines, although you have to be careful with the level of abstraction, but this can be imposed by design software. Just last week, I was playing something that was called, I think, "Bad Golfers." It got into a condition that it couldn't get out of, and I thought, "Buggers didn't understand state machines."

I doubt too many of the people who go into a vo-tech course care terribly much about pinball, though, so I'd have to pick some other examples. Certainly there are plenty of uses in the more lucrative areas of programming.


The truth may be out there, but lies are inside your head.--Terry Pratchett


[ Parent ]
Events vs Threads (4.00 / 8) (#42)
by bugmaster on Mon Nov 18, 2002 at 04:42:18 AM EST

Just like threads, events can be pretty hard to get right. For example, many Windows programs follow this pattern:
void handleButtonClick(...) {
get user's input;
copy a 10Gb file;
return;
}
What happens while this even handler is running ? That's right, the application appears to be totally frozen until the 10Gb file is copied, unable to even redraw itself.

You may say, "silly Bugmaster, the file-copying would be implemented with events as well". However, not many people know to do this, especially when the time-consuming operation is not something as obvious as disk IO. For example, until very recently, Internet Explorer as well as Mozilla would appear to lock up when you would type "www.foo.com" into the address bar and press Enter. Looking up DNS is usually a very quick operation for anyone on a T1, but modem users don't have it so easy.
>|<*:=

Offtopic (none / 0) (#44)
by bugmaster on Mon Nov 18, 2002 at 04:44:21 AM EST

Out of curiosity, what is the equivalent of <pre> in k5-html ? Writing out <blockquote> around code blocks by hand seems like more of a hack...
>|<*:=
[ Parent ]
Have you got access to Perl? (5.00 / 1) (#114)
by AndrewH on Mon Nov 18, 2002 at 09:25:37 AM EST

If so, you can try using this script.
John Wilkes Booth, Lee Harvey Oswald, John Hinckley Jr — where are you now that we need you?
[ Parent ]
Thanks (none / 0) (#181)
by bugmaster on Mon Nov 18, 2002 at 03:57:13 PM EST

Thanks, the script is a nice thing to have. However, some people's comments on that thread are correct -- we really do need some sort of an official solution. Plus, there is no way that I know of to get this script working automatically in the browser... Creating text files and running the script over them is really too much hassle for comments, IMHO.
>|<*:=
[ Parent ]
Events and Threads are Both Tricky (5.00 / 2) (#45)
by moshez on Mon Nov 18, 2002 at 04:46:18 AM EST

...as mentioned in the article. However, thankfully, if the bug above is made, it is easy to reproduce ("you're saying that it gets stuck when you press that button? could you send me the file? thanks"), easy to debug ("ho hum, it does indeed get stuck. let's step in with the debugger and see where") and fairly easy to solve (using, for example, the producer/consumer pattern). Most importantly, the problem, once found is obvious. Perhaps the newbie programmer will have to look and see how to solve this problem -- but as we all know, the hardest part of debugging is not solving the problem, but understanding the problem.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Not Neccessarily (3.50 / 2) (#56)
by bugmaster on Mon Nov 18, 2002 at 05:06:08 AM EST

Just like threading issues, this bug depends on the current system load and machine configuration. For example, a T1 user won't notice the DNS-locking-up bug until everyone in the office starts downloading pr0n, thus saturating the previously fast network connection. When you try to debug the bug, it will be gone. And who knows which of the dozens of event handlers that handle the pressing of the button (GUI handlers, mouse handlers, the actual button handler, etc. etc.) causes the bug ? It would be especially bad if the event handlers in question send their own events to other parts of the system (and they usually do).

Of course, once we identify the precise event handler which is responsible for the bug, we can fix it. The same is true of threading bugs, however. "Once found" (and understood), any bug is "obvious".

Perhaps the real issue here is not multithreading per se, but bad support for threading in Windows/Unix. I imagine if the cherished event-based code was mired in obscure "WaitForFooBarBaz (LPARAM, WPARAM_that_is_really_an_LPARAM, this_parameter_is_always_null_unless_its_not)" calls, it would be annoying to use as well.
>|<*:=
[ Parent ]

Quite Possibly (5.00 / 3) (#57)
by moshez on Mon Nov 18, 2002 at 05:07:37 AM EST

If threading was better implemented, my article would have been stupid. For example, if we all used Oz or E, then, by all means, I would have been wrong to argue threads leads to bugs.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Threads don't kill programs (3.55 / 9) (#64)
by Herring on Mon Nov 18, 2002 at 05:22:20 AM EST

Programmers kill programs.

It is possible to write good multithreaded code, but it's not easy. All the using multiple processes does for you is makes sure that your variables/objects can't be shared between threads. It is possible to design this in a lose the overhead of a process switch (which on Win32 is a lot slower than a thread switch).

Doing difficult things is hard.


Say lol what again motherfucker, say lol what again, I dare you, no I double dare you
It's Also Possible to Write 10000 Lines.... (4.66 / 6) (#68)
by moshez on Mon Nov 18, 2002 at 05:26:03 AM EST

...of bug-free assembly code.

All things are possible, except skiing through a revolving door.



[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
That's true. (5.00 / 3) (#88)
by Herring on Mon Nov 18, 2002 at 06:54:51 AM EST

I think my point is "threads are dangerous in the wrong hands". There's these big myths about how VB.NET or Java makes all this stuff simple. Well it doesn't - it's hard no matter what language you do it in.

Slightly offtopic, but a while ago I thought it would be interesting to test what happened when you called a VB6 object from an ASP page (IIS4) and stuck a load tester on it. It created a separate thread for each user. When it got to around 1,000 virtual users (and 1,000 threads) IIS just upped and died (not surprising with that number of threads). According to MS though it is "easy" to write "scalable" applications using ASP/VB. Yeah, right.


Say lol what again motherfucker, say lol what again, I dare you, no I double dare you
[ Parent ]
Oh, More Power to You Brother (5.00 / 4) (#91)
by moshez on Mon Nov 18, 2002 at 07:14:33 AM EST

We need more people proclaiming from the rooftops that neither Java nor C# nor VB.NET nor (whatever whoever came up with this time) make threading "trivial", "easy" or "intuitive".

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
-1: Complete and Utter Nonsense (4.05 / 19) (#87)
by ewhac on Mon Nov 18, 2002 at 06:48:38 AM EST

This article is a complete load of fetid rabbit droppings.

I've been doing multi-threaded programming since the Amiga came out. Yes, if you're not skilled in its use, multi-threading will bite you in the ass -- just as multiple inheritance, overloading versus overriding, pure virtual methods, and all the other vagaries of object-oriented programming will bite you in the ass if you're not skilled in its use.

To proclaim multi-threading as bad is also to proclaim interrupts as bad, since interrupts are conceptually a thread with a restricted execution environment. Without interrupts, you're reduced to polling, which is a damn wasteful way of collecting asynchronous events. It would also preclude the use of multi-processing (multiple concurrent CPUs) and any other form of distributed computing.

The poster's "solutions" to the "problem" of threading are laughable:

  • Processes
    A process is a thread, with extra baggage. If you're worried about deadlocks or performance loss through serialization -- issues raised by the poster to decry threading -- moving to a process model buys you absolutely nothing, except extra context weight to throw around.
  • Event-Based Programming
    This works only if your program is inherently event-based. If you're writing a game, a parallelizeable computation, or other free-running application, then, again, you get exactly nothing from this model. The poster also claims that, because the event dispatcher assures that no other code is executing, concurrency issues go away (suggesting that his real objection is pre-emption). But this doesn't save you either, as there are no end of ways to deadlock a multi-tasking system even where pre-emption is absent. Finally, a multi-tasking system can be thought of as a sophisitcated event-based system, where the events are scheduling timers or interrupts going off.
  • Cooperative Threads
    Please. This was tried on the Mac and Windoze 3.1. They were an utter disaster, and thoroughly discredited. Because none of the system services could know when -- or if -- they would receive CPU time, making them work at all was an exercise in frustration. You try writing a networking stack without being able to respond to incoming packets as they arrive (as opposed to whenever the rest of the system gets around to letting you check the receiver silos).

Yes, you have to be cautious. But this is true with all programming tools. There is not -- nor will there ever be -- a programming environment that will save you from your own lack of skill, no matter what the sales brochures say. Multi-threading has proved itself to be an invaluable tool, and well worth the trouble it takes to learn to use it well.

Schwab
---
Editor, A1-AAA AmeriCaptions. Priest, Internet Oracle.

complete and utter sense! (3.37 / 8) (#93)
by joto on Mon Nov 18, 2002 at 07:21:31 AM EST

Yes, if you're not skilled in its use, multi-threading will bite you in the ass -- just as multiple inheritance, overloading versus overriding, pure virtual methods, and all the other vagaries of object-oriented programming will bite you in the ass if you're not skilled in its use.

So, you are suggesting that we should recommend to use those features in most situations where they are not needed as well?

To proclaim multi-threading as bad is also to proclaim interrupts as bad, since interrupts are conceptually a thread with a restricted execution environment.

Oh, but they are. And almost universally, people view interrupts as bad. The way we avoid interrupts is by having an operating system.

Of course, when writing an operating system, interrupts aren't bad (although you would typically want to localize them as much as possible). But the operating system is written to shield the application programmer from the intricacies of hardware, and that includes interrupts!

A process is a thread, with extra baggage. If you're worried about deadlocks or performance loss through serialization -- issues raised by the poster to decry threading -- moving to a process model buys you absolutely nothing, except extra context weight to throw around.

But that extra baggage exists for a reason. And that is to minimize both the chances of errors to occur, and to minimize their risk of taking the entire system with them, if they occur. So yes, it buys you something. It buys you simplicity, maintainability, separation of concern, debugability, independent testing, safety, and lot's of other good things.

This works only if your program is inherently event-based. If you're writing a game, a parallelizeable computation, or other free-running application, then, again, you get exactly nothing from this model.

I haven't written many games, but it seems to me that event based programming is really how games ought to be written. You have a refresh-timer. A timer for when objects move. And you are constantly reading input from the user. How else would you structure a game? I can see nothing but added complexity by going in the path of one thread for each.

But this doesn't save you either, as there are no end of ways to deadlock a multi-tasking system even where pre-emption is absent.

Without pre-emption, you don't need locks. Thus there per definition cannot be deadlocks.

Finally, a multi-tasking system can be thought of as a sophisitcated event-based system, where the events are scheduling timers or interrupts going off.

Yes.

Cooperative Threads
Please. This was tried on the Mac and Windoze 3.1. They were an utter disaster, and thoroughly discredited. Because none of the system services could know when -- or if -- they would receive CPU time, making them work at all was an exercise in frustration.

Please. Do you have any idea what you are talking about? Win 3.1 broke down because of lack of memory protection (exactly the same problem occuring with threads) (and sometimes it also broke down because of processes not giving up their time-slice). But this can in no way be compared to a system where all the running cooperative threads are designed to be run together.

Yes, you have to be cautious. But this is true with all programming tools. There is not -- nor will there ever be -- a programming environment that will save you from your own lack of skill, no matter what the sales brochures say.

Oh, yes. There are plenty. Do you seriously mean that it isn't easier to program in Python than it is to program in assembly language? Of course, no programming tool will ever save you from every mistake. But most go a long way towards making it easier than it could be. And I see no reason to discredit them for that (no matter how 1331 your amiga skillz R).

[ Parent ]

A correction (4.50 / 2) (#109)
by scheme on Mon Nov 18, 2002 at 09:07:56 AM EST

But this doesn't save you either, as there are no end of ways to deadlock a multi-tasking system even where pre-emption is absent. Without pre-emption, you don't need locks. Thus there per definition cannot be deadlocks.

Even without pre-emption it is possible to deadlock. Just put a yield statement statement between two (or more) resource acquistions. If you think about it, adding yield statements to a program is the logical opposite of adding a blocking primitive. Thus, adding any yield primitives to a program raises the possibility of deadlocking since any yields can be replaced with lock/unlock primitives.


"Put your hand on a hot stove for a minute, and it seems like an hour. Sit with a pretty girl for an hour, and it seems like a minute. THAT'S relativity." --Albert Einstein


[ Parent ]
Ok, I stand corrected... (4.33 / 3) (#117)
by joto on Mon Nov 18, 2002 at 09:36:03 AM EST

Not that any sane programmer would ever do anything like that. But if it's an implicit yield, which is pretty typical in these kind of systems (e.g. for blocking I/O), this could definitely become a problem.

[ Parent ]
Question (3.33 / 3) (#116)
by bayankaran on Mon Nov 18, 2002 at 09:30:59 AM EST

I've been doing multi-threaded programming since the Amiga came out. Yes, if you're not skilled in its use, multi-threading will bite you in the ass -- just as multiple inheritance, overloading versus overriding, pure virtual methods, and all the other vagaries of object-oriented programming will bite you in the ass if you're not skilled in its use.

So how did you become skilled in its use...was it a feature/capability you got from birth?

[ Parent ]
Picking (very minor) nits... (4.66 / 3) (#154)
by warrax on Mon Nov 18, 2002 at 12:16:13 PM EST

Without interrupts, you're reduced to polling, which is a damn wasteful way of collecting asynchronous events.

Actually, polling is a legitimate technique for speeding up handling of asynchronous events. If you are expecting lots of interrupts it is both faster and more efficient to use polling to collect them. Why? Well, interrupt handling is extremely slow compared to polling. This is true for (at least) the IA-32 architecture and probably on most other architectures.

The optimal behaviour is actually to poll for the same amount of time that it would take to handle an interrupt, and only switch to relying on interrupts when that time has elapsed without receiving any events. (I can't be bothered to look up a reference for this, but a bit of Googling would probably yield a reference.)

-- "Guns don't kill people. I kill people."
[ Parent ]

Yep! (4.00 / 1) (#174)
by Dolohov on Mon Nov 18, 2002 at 03:09:58 PM EST

On some chips, too, you're forced into a combination of both: you get a generic interrupt (that is, one or more of ten) and then you poll the ten specific interrupt lines in order of precedence/handler speed. Much faster than purely interrupt-driven, and often faster than pure polling (that is, you're not polling when nothing is pending)

[ Parent ]
For you thread fans out there... (4.54 / 11) (#96)
by glyph on Mon Nov 18, 2002 at 07:35:51 AM EST

Many of you seem to have multiple CPUs which you want to see "utilized" by a system.  I have a better question for you.  You see, you're just home users with no real consideration of scale.  What's that you say?  You care about multi-processing?  You have 8 CPUs?

I have 8 hundred CPUs, and each one happens to be connected to a different memory bank.  So, threads are a solution for managing concurrency on a particular scale: too big to run on an end-user machine, too small to take advantage of distributed processing.

Luckily I hired some shmoe, like moshez, who doesn't understand whatever model you've developed for verifying the correctness of threaded programs.  (I hear this being brought up by lots of people -- anyone care to provide a link?  He already addresses locks and why they're broken, so as far as I can tell, "threads are well-understood" is code for "I don't know how to design for testability")  Since he thinks threads are dangerous, he writes my program on his single-processor laptop and uses cooperating processes and events.

To his surprise, this means that it now runs just fine on my massive cluster!  Byte-streams are a powerful tool, since their semantics can be relatively simply replicated across machines.  Threads cannot.

For those of you who absolutely positively cannot live without the ability to efficiently exchange huge amounts of data between cooperating processes in core... that's what shared memory is for.  Guess what: it works with cooperating processes.

Threads are not always a bad idea.  If you're a kernel developer, or a programming language implementor working on a new mechanism for safely managing concurrency, or a systems engineer with very clear and simple guidelines for an application that runs in a single-process but multi-thread OS (such as the Windows variant that runs on the XBox, if I recall correctly), these are all potentially good reasons to use threads.  However, sometimes doing your own buffer management in C or hand-writing assembler code are also appropriate.  This does not mean that the average application developer will experience that situation in their lifetime.

Microsoft has a model based on petri nets (none / 0) (#104)
by MfA on Mon Nov 18, 2002 at 08:34:10 AM EST

They introduced a framework based on petri-nets to detect deadlock and race conditions a long time ago, of course very few developers actually use such frameworks.

It would be so much better if the language definition made sure that this kind of formal reasoning could take place though, like Occam for instance. If only the PC had been based on the transputer :)

[ Parent ]
Your solutions have problems also (3.50 / 2) (#113)
by scheme on Mon Nov 18, 2002 at 09:16:15 AM EST

To his surprise, this means that it now runs just fine on my massive cluster! Byte-streams are a powerful tool, since their semantics can be relatively simply replicated across machines. Threads cannot. For those of you who absolutely positively cannot live without the ability to efficiently exchange huge amounts of data between cooperating processes in core... that's what shared memory is for. Guess what: it works with cooperating processes.

The moment you introduce shared memory, you can deadlock. You need some sort of synchronizing primitive to make sure that two processes don't access the same piece of shared memory simultaneously.

Even your separate process model has potential deadlocks even if the processes don't have any shared memory. You just need to allow processes to acquire (limited) resources in different orders and that will potentially allow a deadlock to occur.


"Put your hand on a hot stove for a minute, and it seems like an hour. Sit with a pretty girl for an hour, and it seems like a minute. THAT'S relativity." --Albert Einstein


[ Parent ]
Yes, the Solutions Have Problems (5.00 / 1) (#120)
by moshez on Mon Nov 18, 2002 at 10:33:03 AM EST

Yet you only seem to point out the problems in the last part, where glyph tried to show that you can get some of the benefits of threads with processes. Sadly, the honey never comes without the sting -- in the form of much tighter coupling. This is why I (starring as the dime-an-hour programmer in glyph's story above) didn't use shared memory to do this -- oh no, being a simple programmer, afraid of the tiniest error-inducing technology, I used a saner method.

Let us assume, for the sake of this excercise, that I used the Twisted event-based networking toolkit to write the program glyph hired me to write. Of course, when I needed to communicate between the different processes I didn't do anything as foolish as shared memory -- I used Perspective Broker, Twisted's native remote object framework, to communicate between objects. Now, granted, I could still "deadlock" if my program had a real bug, such as waiting for the result of some operation before performing that operation. However, thank to the nature of how Twisted works, I could use the Manhole service to probe into the objects and see exactly which callbacks are being waited on, and who is generating them.

If you were trying to prove that when using shared memory, you still get (free of charge!) some of the inherent problems with threads then I wholeheartedly agree.



[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Not quite (3.00 / 2) (#136)
by scheme on Mon Nov 18, 2002 at 11:34:26 AM EST

This is why I (starring as the dime-an-hour programmer in glyph's story above) didn't use shared memory to do this -- oh no, being a simple programmer, afraid of the tiniest error-inducing technology, I used a saner method. [...] Of course, when I needed to communicate between the different processes I didn't do anything as foolish as shared memory -- I used Perspective Broker, Twisted's native remote object framework, to communicate between objects. Now, granted, I could still "deadlock" if my program had a real bug, such as waiting for the result of some operation before performing that operation.

Using something like twisted would also remove most of the performance benefits of using a tightly coupled parallel system. Instead of using cpu interconnects with latencies on the order of a few hundred nanoseconds you are now going through the network stack with latencies on the order of hundreds of microseconds to a millisecond.

Also that doesn't remove the possiblity of deadlock. Anytime different processes try to acquire limited resources(exclusive access to files, hardware resources, system resources, etc.) in different orders, you have the possibility of a deadlock.


"Put your hand on a hot stove for a minute, and it seems like an hour. Sit with a pretty girl for an hour, and it seems like a minute. THAT'S relativity." --Albert Einstein


[ Parent ]
Utilising CPUs (2.00 / 2) (#139)
by Simon Kinahan on Mon Nov 18, 2002 at 11:37:31 AM EST

You see, you're just home users with no real consideration of scale. What's that you say? You care about multi-processing? You have 8 CPUs?

You're missing out medium-sized machines (roughly 4-128 CPUs) in your analysis. The thing is, these systems matter, because they're the ones corporate databases, web applications and web servers typically run on. Communicating processes work fine as a model for the web server, and up to a point for the database, but they work much less well in the middle tier. And, indeed, it is here that large-scale multithreaded systems (often written in Java) tend to reside.

The reason threads are used in this situation is basically resource pooling. You've got requests coming in at some rate from the web server, and you've got N database connections, X gigabytes of memory and M CPUs to service those requests as fast as possible. Dropping and opening database connections costs, so you pool them. Given that this is generally Java we're talking about, allocating and deallocating objects costs too, so you pool them.

Now you've just got to share out those CPUs. Unfortunately, your object pool and DB connection pool (plus statement pools, data caches, and so on) are only available in one process. If you split the system up into multiple processes, those pools have to be split up too, which results in inefficiencies, if the process handling one request has run out of resources, but they are still available in some other. So you add a thread pool, with 1.x (where x is small) threads per CPU, and dispatch incoming requests through that.

Now, it must be said that this not ad-hoc multithreaded programming. Its actually much more similar to multiple processes, because those incoming requests share state only in very carefully structured ways (through the database, mainly).

I hear this being brought up by lots of people -- anyone care to provide a link? He already addresses locks and why they're broken, so as far as I can tell, "threads are well-understood" is code for "I don't know how to design for testability"

Heh. In many cases, I'm sure that's true. Using threads means adopting some kind of strategy for controlling their interactions. Precisely what that should be depends on the application, but just letting threads roam around the data at random, bumping into one another, is a Bad Idea. It is generally a Good Idea to limit their scope for interaction as far as possible.

It is also a Good Idea to think very hard about the failure modes the interactions between your threads can produce. There are some formalisms, such as Petri nets, or CCS, to model concurrent systems, but generally they only work for really small ones, so by and large you must do this by hand.

Simon

If you disagree, post, don't moderate
[ Parent ]

C++ am teh sux, 2!!!11 (2.75 / 4) (#223)
by daniels on Mon Nov 18, 2002 at 07:20:51 PM EST

This is from someone who thinks C++ sucks, because he wrote an allegedly humourous anecdote on some wiki somewhere. If you listen to glyph, we should all be sitting around banging rocks together, and either writing our own string handling routines (because we're MEN!), or using languages so abstracted you *need* his 800-way cluster to run "Hello world!". The need for threads crop up more often than you'd think, and no amount of FUD will counter that.
--
somewhere in space, this may all be happening right now
[ Parent ]
Ad-Hominem is TEH SUX!!!11!!!! (n/t) (4.00 / 1) (#268)
by moshez on Tue Nov 19, 2002 at 01:41:18 AM EST



[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Threads are being pushed into hardware (4.00 / 5) (#107)
by HidingMyName on Mon Nov 18, 2002 at 09:02:13 AM EST

Intel is promoting hyperthreading products, which have been in labs and high performance computing (Tera used them) for a while. The idea is that memory and L2 cache latencies are now large relative to the processor's clock speed (an order of magnitude or more). In order to improve performance and to make the processor latency tolerant, they encourage programmers and compiler writers to express programs as a collection of fine grained threads. The threads are scheduled in hardware, so that when a cache miss occurs a very fast context switch occurs by bank switching a window into a large array of registers, which selects the user visible subset of the registers.

Threads can be complex and require special care and analysis (since concurrency is often hard to get right), however it is important to realize that for certain classes of problems, multithreaded solutions can be easier to use, and that threads have the potential to exploit available performance for some architectures

I Said in the Article (4.66 / 3) (#108)
by moshez on Mon Nov 18, 2002 at 09:06:20 AM EST

That there is a class of problems for which threads are the correct solution. It's just that this class is very, very small -- especially compared to what people think it is.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
The class varies as architecture evolves (5.00 / 1) (#168)
by HidingMyName on Mon Nov 18, 2002 at 02:13:37 PM EST

The class of problems suitable for threading just got a whole lot bigger when hyperthreading was released to the general public in a commodity processor.

The inescapable problem is that device miniturization and speedup, coupled with a strict upperbound on signal propagation velocity, means that the relative speeds of memory access and L1 cache times are going to be drastically different. Traditional approaches use dynamic scheduling, which stalls an instruction when a functional unit or value is not available. What we would like is for a dynamic scheduler to tolerate a large number of stalls, so that out of order execution would be permitted if possible, if that value is too small the whole instruction stream stalls. However, in practice, dynamic scheduling and exploiting instruction level parallelism transparently in the presence of a number of functional units has a combinatorial explosion when as the number of tolerated stalled instructions grows. As the processor speed is becoming increasingly faster than memory access time, this complexity can no longer be handled in the processor. There must be software solutions to exploit fast parallel execution at a finer granularity than before. Multithreading at the lowest level is going to be very necessary for performance, however, at a higher level, there is hope that using high level languages with smart code generation will allow us to continue to recompile and run our existing source.

[ Parent ]

Hyperthreading (none / 0) (#312)
by Znork on Tue Nov 19, 2002 at 08:39:24 AM EST

No, not really. The number of computers gain that the ability to benefit from a specific way to solve that class of problems just got a whole lot bigger. Not the class of problems in itself.

Hyperthreading sounds good, but in reality the only performance benefit will come when you have small problems that can be solved within the registers (preferably) or the L1 cache. Since you have two execution units fed from the same L1 cache you now have effectivly half the L1 cache for any memory bound problem. Now, for most programs, you have two units stalling instead of one.

The set of problems where threading is good or necessary remains the same (of course, since some parts of OS kernels can be included in that set there might be some performance benefit to hyperthreading CPU's if well done).

[ Parent ]

Depends on how much of a win hyperthreading is (5.00 / 1) (#418)
by HidingMyName on Fri Nov 22, 2002 at 11:33:17 AM EST

No, not really. The number of computers gain that the ability to benefit from a specific way to solve that class of problems just got a whole lot bigger. Not the class of problems in itself.
But suppose I have a machine class x, and there is an optimization that greatly improves program performance for programs run on x. If x is widely deployed, then the class of programs which should exploit that optimization is large. In a sense you are right, but in another sense, if the optimization feature was not widely available, researchers and developers would not try to apply it. So now they have an opportunity (and perhaps a lot of work) to try to exploit that feature on previously untried problems. So although the class of problems hasn't changed, the de facto class of problems which people will work on has. In practice, this de facto class is the only class that matters to people outside the lab (I'm in the lab so I get to see the cool stuff however). I suspect the features success/failure will reflect the speedup obtained by using the feature, how hard it is for those programs to exploit that feature. Additional resistance may come if legacy machines without that feature are widely deployed and either cannot run software targeted for the new architecture or suffer a major performance degradation when running programs optimized for the newer architecture.
Hyperthreading sounds good, but in reality the only performance benefit will come when you have small problems that can be solved within the registers (preferably) or the L1 cache. Since you have two execution units fed from the same L1 cache you now have effectivly half the L1 cache for any memory bound problem. Now, for most programs, you have two units stalling instead of one.
Do you have references on that, it is an interesting remark, but I'm not sure if you are right or not. Playing devil's advocate here, let me give a counter argument. In practice doubling the cache size often increases the hit rate by a small percentage (for large caches there is a law of diminishing returns on the hit rate). Hyperthreading is a fine grained variant of threading, so I would expect compilers to do things like analyze basic blocks and extract short threads that can be run concurrently for example. In that case, speculating wildly, I'd expect the number of hyperthreads running for a given process to be relatively small (say 1-10 or so, with 2 or 3 being common). There may be interblock/interprocedural analysis that goes on as well. I think developers may eventually be expected to give hints to the compiler to improve decomposition. I think debugging will be one hard part of working in hyperthreaded environments.

[ Parent ]
Typo (3.50 / 2) (#175)
by ubu on Mon Nov 18, 2002 at 03:27:41 PM EST

It's just that this class is very, very small

Are you sure you don't mean "large"?

Pervasive thread use is a net design and performance win, not only because it provides easy access to parallelism and finer-grained control than at the process level, but because it lends itself well to modular design methodologies. Well designed thread-based classes can be used seamlessly with other code as long as they are clear about the contract they present. Your entire article could be summed up by saying that you write incestuous, tangled code, and that threads make debugging said code more difficult.

You'll hook a lot of skeptics, of course, as well as users of archaic languages with little or no real support for threads (C, C++), but those of us who have re-learned our design methodologies in light of new languages and patterns understand almost intuitively that threads are an important and powerful part of any functional program.

Parallelism is difficult. Why didn't you just shorten your article to those three words? Every introduction to threads I've ever read has been clear about the dangers of deadlocks, priority inversion, and other problems in parallel systems. They're as old, really, as multi-tasking. Is multi-tasking considered harmful? Should we venture back into cooperative process management?

Ubu


--
As good old software hats say - "You are in very safe hands, if you are using CVS !!!"
[ Parent ]
Difficulty (4.00 / 2) (#307)
by Znork on Tue Nov 19, 2002 at 08:15:40 AM EST

Indeed, parallelism is difficult. I agree that the article could probably even have been shortened to that. In fact it's so difficult it should probably only be practiced to any greater extent by a small elite, and maybe by others for very limited and specific subsets of tasks after careful analysis.

In the discussions around this article there are several examples of creating threads to solve nonexistent problems. For simple things like multiplexing IO or multiplexing UI you have good examples where parallelism does not have to be used unless there are other compelling reasons to use it.  These are good examples where there has been little or no analysis of the problem _at_ _all_, and if programmers dont even think the code structure through that far, how are they going to be able to handle the detailed and careful analysis that is necessary for safe and sane threading (especially in languages that are very unsuited to threading)?

Pre-emptive multitasking and memory protection are good ideas exactly because they provide just the opposite of threads; complete separation between processes. A mechanism which protects you from programming mistakes. Threading is, in effect, a venture back into the good old old days of cooperative process management (in the shape of mutex locking for management) without memory protection (altho on a smaller scale this time).

I agree that threading can be good (and sometimes necessary), but facing the reality where good design is not a common feature to most programs they are, and are likely to remain, a source of far more problems than they solve.

[ Parent ]

a reposted comment (4.77 / 9) (#115)
by joto on Mon Nov 18, 2002 at 09:26:09 AM EST

From this editorial comment by kraant:
For all the people flaming this article answer me one thing. How do you formaly prove a multithreaded program?
...I posted a reply that moshez liked, and would like me to repost, so here it is (editorial comments considered harmful?):

A very good question

Of which an answer here would be too short. But it relies of course on the same methods you would use to formally specify and verify any program, concurrent or not. Add to that some trickery to be able to reason about concurrency, and you have a method (sorry, that was no help, I know). [Note added after repost: If you only worry about the concurrency issues, and not full formal verification, it can often be much easier, see e.g. this post for an interesting alternative view]

Currently, formal methods are in the stone-age. We have no good tools to use. We still haven't invented FORTRAN. We do all the grunt-work ourselves (or with some laughably primitive theorem-prover, which in current generations are so primitive that they mostly can't even be used for teaching introductory logic). And we have no efficient way of keeping proofs in sync with programs. There is of course a lot of research in this area, of which proof-carrying code may be the most promising.

If you want to know more about formal methods, I would wish I had a good link to point you to, but I haven't. I think this goes into the lore of computer science that it is very difficult to learn without having taken a course in it. The course I took (on verifying concurrent programs) relied entirely on compendiums and papers.

Part of the reason is probably that it is a very unpractical process, taking insane amounts of dedication, time and effort. And in the real world, people usually don't have the time and/or money to do that. Unfortunately, for concurrent programs, that isn't true. You have to take the time to formally verify it to be somewhat sure it works, since testing doesn't really help much.

One way to avoid the problem alltogether is to rely on patterns. And that is what most people do. Instead of reinventing the wheel all the time, they keep using the same ideas (producer-consumer, etc...) that has been written and formally verified by computer scientists before (but be careful, try googling for "double checked locking", and see why it is important that the pattern in question has been subject to formal verification). Once you go beyond those well-established patterns, concurrent programming becomes hard.

I might consider writing a future article on the subject if people show enough interest. But I will not promise anything. To condense the material enough to produce a readable article is a major task (although highly needed).

Synchronization by Contract (3.83 / 6) (#166)
by p3d0 on Mon Nov 18, 2002 at 02:09:32 PM EST

I thought you might find this interesting...

Somewhat related to formal program proofs is Design by Contract. DbC is a method for using preconditions and postconditions to reason about (if not formally prove) the correctness of a program. It's something we all do informally and intuitively all the time: I'm going to call this method, so I better set up its arguments properly, and when it finishes, I can be sure it has done X, Y, and Z. Real DbC is just a way of formalizing these notions somewhat.

It is claimed that DbC is not compatible with threads because of race conditions. If I establish a method's precondition, and then call it, how can I be sure another thread won't invalidate the precondition in the mean time?

There are a number of ways to resolve this, but my personal favourite right now is "Synchronization by Contract" (SbC) which simply means writing race-free preconditions.

The main tool of SbC is a query on mutexes called "isMine", that returns true iff the mutex is owned by the calling thread. The crucial thing about isMine is that it is not subject to race conditions: it cannot be made to switch from false to true or vice-versa by any other thread. This is the fundamental building block that allows race-free assertions to be made.

So, every data object is protected by a lock. (I don't mean each object has its own lock; just that for each object, there is some lock that protects it.) Then, each method has the option of putting "lock.isMine()" in its precondition. If the caller has not acquired the appropriate lock before calling that method, an assertion failure will occur. This makes these bugs much easier to find, because they do not require any kind of data race to occur.

For deadlock, the same thing can apply. Each lock would "know" which lock precedes it in the locking order, and the lock() procedure can assert that "predecessor.isMine". Now you have a lock order enforced by race-free assertions, and again, you do not need to reproduce an actual deadlock in order to be alerted to its presence.

So, in short, you can make these races and deadlocks no harder to find than regular assertion failures. Assertion failures are not too hard to find with an appropriate test suite.

I haven't yet done any of these things in practice, but I'm just starting on a project on which I will try implementing these ideas to see how well they work.
--
Patrick Doyle
My comments do not reflect the opinions of my employer.
[ Parent ]

Oops--deadlock (4.00 / 3) (#169)
by p3d0 on Mon Nov 18, 2002 at 02:20:00 PM EST

Come to think of it, the predecessor.isMine() way of avoiding deadlock is no good. It would mean that you have to acquire all previous locks, and that is probably impractical and unnecessary.

However, some kind of SbC-friendly approach is still possible. For instance, every lock could be assigned a number, and the current thread could keep track of its current lock number. Then, the precondition on acquiring a lock would be "lock.number > thread.lastLockAcquired.number" or some such thing.

Oh well. At least the rest of my post was fairly coherent. :-)
--
Patrick Doyle
My comments do not reflect the opinions of my employer.
[ Parent ]

I would like it to work... (5.00 / 1) (#196)
by joto on Mon Nov 18, 2002 at 05:15:41 PM EST

But it doesn't. I always felt disappointed when I came to the chapter in OOSC that described multithreaded programming, and DbC didn't work anymore.

The problem with your approach is that it forces you to acquire the locks in a certain order. This may be ok, if you are willing to arbitrarily rewrite your program to match this arbitrary criterion, even though it already is provably correct, easier to understand in it's current form, and you can live with the performance degrade.

Actually, it is worse. Because if every mutex has a number associated with them, someone has to give them that mutex. If you let the system do that, you can no longer write correct programs on first try! You must actually test them and see if preconditions break before you can know if you acquire the locks in the right order!

Then we have the equally bad solution of letting the programmer assign numbers to those locks (statically). Then you could still write a correct program, but you would be limited to a statically determined number of mutexes, and as each library-writer must manually avoid collision, so we need a global instance handing out numbers to individual programmers to avoid overlap. Again, something you will not live with.

Too bad, because I really would like DbC for multithreading...

[ Parent ]

Addressing deadlock with DbC (none / 0) (#259)
by p3d0 on Tue Nov 19, 2002 at 12:22:11 AM EST

Clearly, dynamically-assigned lock numbers are no good. Design by Contract is a design tool; that is, it's not enough for the objects to know what's going on; rather, the programmer must also know what's going on. Dynamically-assigned lock numbers break this rule.

However, as you say, statically-assigned lock numbers are not much good either. So perhaps lock numbers are not the ticket.

I shouldn't have addressed deadlock in my post, since I had given it only a moment's thought. However, SbC and race-free assertions is something I have thought about for some time now, and I'm pretty convinced it's a useful approach.
--
Patrick Doyle
My comments do not reflect the opinions of my employer.
[ Parent ]

Woah, 1?? (4.00 / 1) (#186)
by p3d0 on Mon Nov 18, 2002 at 04:24:15 PM EST

I got modded at 1 for the parent post. Anyone have any idea why? I wish the moderator had replied.
--
Patrick Doyle
My comments do not reflect the opinions of my employer.
[ Parent ]
Chill (4.00 / 1) (#192)
by Lacero on Mon Nov 18, 2002 at 04:52:29 PM EST

If he replied to every post he's moderated one he'd be here until Christmas.

[ Parent ]
heh (4.50 / 6) (#122)
by boxed on Mon Nov 18, 2002 at 10:49:00 AM EST

I actually laughed out loud just reading the intro text. As for message based programming: I do win32 api programming professionally and I find myself forced to use threads a lot of time there. The number one solution that this document doesn't discuss is the one I use: don't use thread unsafe code and if two threads need to talk, use messages. Simple as that.

As simple as that eh? (4.00 / 1) (#125)
by caine on Mon Nov 18, 2002 at 10:56:52 AM EST

The number one solution that this document doesn't discuss is the one I use: don't use thread unsafe code and if two threads need to talk, use messages. Simple as that.

Personally, I avoid all these problems by just writing Good code. As simple as that. :-) Saying you write thread-safe code doesn't mean you do. As soon as you have threads, there's a risk that your code is unsafe. Using messages is a very good way to get better and safer code, but it's still possible to screw things up.

--

[ Parent ]

eh, no (3.33 / 3) (#128)
by boxed on Mon Nov 18, 2002 at 11:03:57 AM EST

Thread safe code is easy. Don't use static or global variables and you're pretty much home free.

[ Parent ]
No... (5.00 / 1) (#131)
by caine on Mon Nov 18, 2002 at 11:15:20 AM EST

You can still get stupid dead-locks, for example where one thread is waiting for a message from another, and thread number 2 is waiting for the first one. Messages and no shared memory (except for the mailboxes or equivalent) makes things much more unlikely to fail, but there's no such thing as a 100% threadsafe code just by virtue of not using certain kinds of programming constructs. After all, you always share some resources, albeit moste of them abstracted by the OS.

--

[ Parent ]

But (5.00 / 1) (#203)
by speek on Mon Nov 18, 2002 at 05:40:37 PM EST

Deadlocks like that only happen if you have two threads going through the locking mechanisms in reverse order. Ie, thread #1 hits lock A, then lock B, which thread #2 hits lock B, then lock A. I have never had to solve any problems that tempted me to create such code. If my code goes in two directions like that, it is most likely in need of refactoring so that the relationship between the code blocks is one-way. Two-way dependencies and coupling are always trouble.

I may be missing something, as I'm only a business-logic programmer and don't mess around with game, OS, or real-time programming.

--
al queda is kicking themsleves for not knowing about the levees
[ Parent ]

Perhaps you should mess around with them :-) (none / 0) (#321)
by caine on Tue Nov 19, 2002 at 09:44:04 AM EST

No, I agree it's an unlikely situation, and a good programmer would probably never encounter it. It was merly a refute to the statement "if you don't use programming statemtents X and Y your code is threadsafe". As long as you have threads that communicate (even by mistake), there's a risk your code isn't threadsafe.

--

[ Parent ]

Believe it or not (4.50 / 2) (#138)
by moshez on Mon Nov 18, 2002 at 11:37:22 AM EST

I'm glad you laughed out loud. The article was supposed to be somewhat light and to be amusing as well as interesting. It is nice to know I had at least some success.



[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Separate Processes (none / 0) (#171)
by MyrddinE on Mon Nov 18, 2002 at 02:37:29 PM EST

By using message based interaction, aren't you essentially using IPC? Does that mean that your applications could be recoded as separate processes with little effort?

[ Parent ]
Why would you want to? (none / 0) (#180)
by avdi on Mon Nov 18, 2002 at 03:55:14 PM EST

I'm sure he's talking about something like shared threadsafe queues, orders of magnitude more efficient and convenient than multiple processes communicating over pipes or sockets.

--
Now leave us, and take your fish with you. - Faramir
[ Parent ]
Any Data? (5.00 / 1) (#188)
by moshez on Mon Nov 18, 2002 at 04:31:45 PM EST

You're claiming thread-safe queues are "orders of magnitude" more efficient. I'm not a native English speaker, but allow me to use my (perhaps flawed understanding): this means at least 2 orders of magnitude. An order of magnitude, I learned in school, is 10 -- so 2 orders of magnitude would be 100. Do you have any benchmarks showing IPC is 100 times as slow as thread safe-queues?

I assume you do, because you'd never claim such a hard numerical fact without evidence to support it. Let us think what the effects are. How much time do you think a proces or a thread spends sending, or receiving, messages? Frankly, if you need so much communication that it becomes significant, then you're quite possibly with a completely serial event-based implementation.



[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Mmmf.. (none / 0) (#412)
by sudog on Fri Nov 22, 2002 at 01:53:18 AM EST

"forced to use threads"?

Nobody is ever forced to use threads. They back themselves into it by writing shitty code.


[ Parent ]

maybe you should get a job instead? (none / 0) (#413)
by boxed on Fri Nov 22, 2002 at 04:04:02 AM EST

Threading is a HUGE performance increase over multiple proccesses. Maybe you should get a real job where you have limitations and performance requirements? You don't seem to either know what threading is, or you have no clue as to what kinds of code one has to write out there in the real world.

[ Parent ]
Uh.. perhaps you need a quick primer? (none / 0) (#441)
by sudog on Wed Dec 11, 2002 at 02:32:01 AM EST

Dumbass, threading is NOT a gain over event-queue models until the processing requirements exceed that of a single CPU in a multi-CPU environment, in which case a cooperative forking model picks up where the event-queue left off. You're forgetting context switching overhead, dumbass.

Threads are NOT portable, some OSen don't have kernel-supported pre-emptive threads yet, the different threading interfaces are ALL subtly different, and for the record, I have been in a job where there are limitations and performance requirements. Writing C. What are you doing? SQL? Gimme a break. Pussies use SQL.

Threads are only the answers for programming cubicle punks like you. Your own page lists you as a Java/JSP/SQL nerd. What makes you think you can comment until you learn a real language?

Go back to your scripting hole and don't come out  until you learn C and can write an event queue without signals.

You are no Iaido-ka. You are the dirt on my heels.

[ Parent ]

wow (5.00 / 1) (#445)
by boxed on Wed Dec 11, 2002 at 04:45:05 AM EST

It took you a month to come up with "I'm better than you, you suck"?

I might mention that I do C++ for a living, I don't really care about portability beyond windows, osx, solaris and other modern operating systems.

Threads are only the answers for programming cubicle punks like you. Your own page lists you as a Java/JSP/SQL nerd. What makes you think you can comment until you learn a real language?

Not quite. It lists me as doing Java and SQL on my free time. No JSP is not a language like you seem to think, and again, I do C++ for a living. Why don't you learn java so as you don't have to speak out of your ass?

Go back to your scripting hole and don't come out until you learn C and can write an event queue without signals.

I know no scripting languages.

[ Parent ]

No. Just happened to see my comments/replies. (none / 0) (#446)
by sudog on Sun Dec 15, 2002 at 02:14:18 PM EST

I'm not saying you should care about obscure operating systems. However, isn't the threading implementation in Windows different from whatever threading implementation is available in Solaris, and different from the threading implementation available in OS X?

Are you trying to imply that you only have to write software for one of those platforms and then quickly port it to another? Do you expect me to believe you didn't run into any "strangenesses" the first time you tried to do that? Have you run into the "different" (shittier) heap allocation strategy in Visual Studio's compiler yet? How has it impacted your threaded applications?

If you do C++ for a living, then why are you trying to foist off the "threads are better" approach? What I'm curious about is why you haven't already realized that there are better models, easier to debug, even if harder to implement? Have you written an event queue yet?

Or are you trying to tell me that the speed with which a threading model can be written is something in its favour? Well, sure, maybe, if you don't mind the time it takes to hunt down a few elusive bugs or the platform you wrote it on goes under and you have to port it to something else.

And I never said JSP was a programming language. I said you were a Java/JSP/SQL nerd. I don't consider SQL a language either.

And I've already learned Java--been formally trained in it even. Unfortunately the implementation is useless and will be until Sun opens it up. Microshit has their own "Java" that isn't compatible with the JRE. What the hell kind of standardization is that? I thought Java was supposed to be a write-once, run-anywhere solution?

I apologize for the Iaido comment. It was rude and uncalled-for.


[ Parent ]

blahblah (none / 0) (#447)
by boxed on Mon Dec 16, 2002 at 04:49:26 AM EST

Have you written an event queue yet?

I do win32 api for a living actually so I can hardly avoid dealing with event queues now can I? Stop being so damn arrogant about your ability to write a queue, I am not impressed.

And I never said JSP was a programming language. I said you were a Java/JSP/SQL nerd. I don't consider SQL a language either.

Well SQL most definetely is a language, so something is wrong with this picture.

And I've already learned Java--been formally trained in it even. Unfortunately the implementation is useless and will be until Sun opens it up. Microshit has their own "Java" that isn't compatible with the JRE. What the hell kind of standardization is that? I thought Java was supposed to be a write-once, run-anywhere solution?

I bet you got taught to use Vector. I have yet to speak to someone who got GOOD java programming advice from a school of any kind. As for MS incompatible JVM, eh, they DID sue them and MS no longer ships their broken JVM, what more do you want? Do they need to retroactively go back in time and remove it? Be realistic here. I can write an incompatible version of .NET too, the difference being that it'd be legal and no one could stop me from fucking up the market with mutually incompatible implementations.

[ Parent ]

Playing with Windows queues is not writing one... (none / 0) (#448)
by sudog on Sun Dec 29, 2002 at 01:03:49 PM EST

Just because you get to tap into the Windows event queues, doesn't mean you can write one yourself.

SQL is not a language. C is a language. C++ is a language. OCaml is a language. SQL is a toy.

No, I didn't learn vector. I used JRE and the JDK from Sun. I didn't have the luxury of an IDE, either.

Incompatible language implementations are assinine. Write one and you'll be assinine too.


[ Parent ]

no it isn't ... (none / 0) (#450)
by boxed on Tue Dec 31, 2002 at 08:52:18 AM EST

...but it teaches me clearly that all problems aren't solvable with an event queue in a way that will make me earn my wage. And stop bitching about your ability to write a queue for crying out loud, it's a QUEUE not some kind of god you've put together. It's a simple data structure, you sound like it's the end-all solution to programming problems. Get a grip.

Oh ok, so because you don't like a language it becomes a "toy". How nice for you to be the center of the universe like that.

Eh, Vector is included in the JRE from 1.0 and onwards in all implementations including the fucked up one from MS. And as for incompatible language implementations, let's face it, C++ beats EVERYONE to the bush with that one. It was only last year that the first 100% standards supporting C++ compiler was written after all, all the other compilers have been mutually incompatible since the dawn of time. Again, get some perspective. Java has multiple implementations that supports the language to 100% and only one that does not, that one implementation also happen to be ILLEGAL by order of the US justice system. One can hardly say the same thing of the incompatible C++ compiler in microsoft visual C++ environment.

Btw, do you only read k5 twice a month or does it take you this long to come up with these "arguments"?

[ Parent ]

Now what are you going on about..? (none / 0) (#451)
by sudog on Fri Jan 03, 2003 at 05:18:42 PM EST

An event queue is not just a queue, it's a queue, the supporting routines that interact with the queue, a running loop based on events in the queue, time-skew detection (if applicable) and so on and so forth.

Write one and then come back and tell me it's no big deal. :)

And I like SQL just fine. I use it almost everyday and have built large database-driven sites with SQL backends, as well as high-end, high-use databases for local law-enforcement organizations.

It's still a fucking toy.

*shrug* Well, whatever is included with the JRE is fine, I never used what you are continuing to fail to describe adequately: Vector. Perhaps you're calling it something it isn't? Or implying Vector is something it isn't? Are you talking about the vector class in Java? What's wrong with using vectors?

And just because C++ has incompatible implementations doesn't justify Java's incompatible implementations. What is your fucking point? Besides, Sun's locked down Java to the point where it's not a free language..  at all.

And whose arguments are pathetic? Do you know what the hell you're talking about? Are you missing a few too many braincells?

Tell me again what your fucking point is? Java is useless. SQL is a toy. You don't have a clue, and you've made nothing but baseless assumptions from the first mesasge you stupidly opened your mouth in.

Go back to your playpen, petulant child, I'm done this little pissing contest.


[ Parent ]

sudden surge suddenly occurs (3.00 / 3) (#123)
by crazycanuck on Mon Nov 18, 2002 at 10:50:06 AM EST

gotta love those aliterations...

deadlocks (3.25 / 4) (#126)
by tzanger on Mon Nov 18, 2002 at 10:57:49 AM EST

I'm not a programmer by trade, but what is wrong with resolving the "I have A, waiting for B" and "I have B, waiting for A" deadlock by making your locking code do something like this:

    If I can't get both locks, unlock what I have and sleep for x us/ms/s/whatever makes sense
?



Sorry (5.00 / 4) (#132)
by arheal on Mon Nov 18, 2002 at 11:17:49 AM EST

The problem with doing this is that the code may have modified global data structures after grabbing lock A and they may now be in an inconsistent state. When the attempt to lock B fails (and both locks are released) another thread my gain A and attempt to use those (inconsistent) data structures. Crashage is now imminent.
There can be only one!
[ Parent ]
And the way around that would be.. (3.00 / 2) (#141)
by kraant on Mon Nov 18, 2002 at 11:41:11 AM EST

And to avoid that, you could only allow data to be changed if a thread has a lock on all the data it accesses.

You could call it Atomic changes.

This is starting to remind me of resource management for processes.

--
"kraant, open source guru" -- tumeric
Never In Our Names...
[ Parent ]

Yes, everything would be so much more easy... (4.00 / 2) (#176)
by joto on Mon Nov 18, 2002 at 03:33:06 PM EST

...if we had versioned memory with transactions and rollback...

Oh, wait. I've seen that too. People using databases as RAM. It was not pretty...

[ Parent ]

Y'know (none / 0) (#213)
by kraant on Mon Nov 18, 2002 at 07:00:23 PM EST

That isn't a bad idea. If I ever write a thesis mind if I steal it?
--
"kraant, open source guru" -- tumeric
Never In Our Names...
[ Parent ]
Oh yes. (none / 0) (#347)
by joto on Tue Nov 19, 2002 at 02:41:13 PM EST

Trust me, it's the single worst idea I've ever seen. It may sound neat in theory, but believe me, it is not. Feel free to steal it, just don't let me work on it....

[ Parent ]
my understanding is (5.00 / 1) (#133)
by cicero on Mon Nov 18, 2002 at 11:21:11 AM EST

that it's up to the thread library. I think posix has some sort of lock_acquire that will return with a failure if it can't get the lock (and i'm sure there are others) but for the most part, the lock_acquire just puts the calling thread to sleep if it can't get the lock b/c it assumes that the thread needs that lock to continue.


--
I am sorry Cisco, for Microsoft has found a new RPC flaw - tonight your e0 shall be stretched wide like goatse.
[ Parent ]
Basically .... (3.83 / 6) (#144)
by Simon Kinahan on Mon Nov 18, 2002 at 11:51:22 AM EST

... there are small number of ways to avoid deadlocks, but ensure that corrupt data is never exposed to other processes. The approaches are:

1. Always claim the locks in the same order. If everyone locks A before B, there's no deadlock problem.

2. Detect deadlock situations and undo any work done by one of the dealocked processes

3. Claim all the locks a process needs simultaneously at the beginning, and unlock them together at the end.

Your idea is basically (2), but there's a subtlety: if your process needs to change A and B in order for the data to be consistent, if it cannot get B, and wants to unlock A, it needs to undo any changes it made to the data that A protects.

Sometimes this (rollback) is not appropriate. You may already be committed to a course of action, or have already notified another process of your actions. Precisely which approach to take is dependent on the application. Doing none of the above is usually a recipe for disaster (someone is now going to tell me an answer I forgot ...)

Simon

If you disagree, post, don't moderate
[ Parent ]

confused (none / 0) (#353)
by tzanger on Tue Nov 19, 2002 at 03:55:06 PM EST

1. Always claim the locks in the same order. If everyone locks A before B, there's no deadlock problem.

What's the point of having two locks then? If something needs only B, it must now lock A as well or risk deadlocking?



[ Parent ]
No (4.00 / 1) (#354)
by Simon Kinahan on Tue Nov 19, 2002 at 04:43:53 PM EST

Sorry, I didn't express that as well as I should have. The rule is, if you need BOTH A and B, you need to claim A then B, never vice versa. You can release them in whatever order you like.

Simon

If you disagree, post, don't moderate
[ Parent ]
Multiple Java Processes Considered Harmful (4.00 / 7) (#135)
by Burning Straw Man on Mon Nov 18, 2002 at 11:33:24 AM EST

Given the choice between multiple Java processes and the dangers of multiple threads (which I admit exist) I will choose multiple threads nearly every time.

Although with the new asynchronous IO library in Java 1.4, most of the reasons I used threads before are now moot because I can use channels for event-driven applications very, very easily. However prior to Java 1.4, if you weren't using multiple threads, your application was a joke waiting to be left on the bottom of the pile labelled "application which does not scale".
--
your straw man is on fire...

Yes, Java Sucks (n/t) (2.00 / 6) (#143)
by moshez on Mon Nov 18, 2002 at 11:47:25 AM EST



[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Gee ... (3.66 / 3) (#145)
by Simon Kinahan on Mon Nov 18, 2002 at 11:52:11 AM EST

That was constructive.

Simon

If you disagree, post, don't moderate
[ Parent ]
Threads don't scale. (none / 0) (#197)
by moxie on Mon Nov 18, 2002 at 05:17:46 PM EST

Actually, Java apps that use a thread per connection are the ones that doen't scale. If you're writing a server in Java 1.4, your only choice is to use native code for multiplexing I/O, and dedicated threads with event callbacks on single I/O sources.


--
http://www.thoughtcrime.org
[ Parent ]
Less than Java 1.4 (none / 0) (#198)
by moxie on Mon Nov 18, 2002 at 05:19:13 PM EST

That should say "less than" Java 1.4.


--
http://www.thoughtcrime.org
[ Parent ]
thread per connection (none / 0) (#205)
by Burning Straw Man on Mon Nov 18, 2002 at 05:59:17 PM EST

The way I generally write a multi-threaded server is:

A. have 1 to N threads listening at the connection socket for new connections. Let us place an upper bound on N, but usually 1 to 4 threads will be active.

B. Those "listener" threads only purpose in life is to accept connections and pass the socket into a queue.

C. Another group of 1 to M threads are listening at the queue. When new connections enter the queue, if there are available active threads not doing work, one of them takes the socket and performs the requested action. If there are no available threads, a new thread (or more likely a set number of threads) will be created, up to that group of M threads.

This is a very scalable architecture and has been used quite widely, with quite some success. It is not as scalable as, say, the event-driven IO which you spoke of, but as you said, in Java < 1.4 you would have to write native code for each operating system to perform that kind of action.
--
your straw man is on fire...
[ Parent ]

How scalable is 'scalable'? (none / 0) (#233)
by moxie on Mon Nov 18, 2002 at 08:32:18 PM EST

First of all, it doesn't make sense to have more than one thread accepting connections on the listener socket. The work that a thread does when it accepts a connection in your model doesn't involve any I/O, so having more than one thread do it will only increase scheduling overhead (it seems).

Second, the goal of any truely high-performance server should be to separate all I/O work from non-I/O work. The work queue model is good, but only non-I/O work should go on the queue, and there should only be number_of_cpus+1 threads draining the queue.

If any of the work chunks on the work queue need to do I/O and async I/O primitives aren't available, I've found that it's best to emulate the async I/O primitves by burning a single dedicated thread on the I/O source. It takes an I/O request off the queue, executes it, and then puts a continuation back onto the main work queue.

This way, you only have as many threads as there are I/O sources (one for the disk, one for the network, etc) - and then a thread for each CPU on the machine.

In the model you describe, it seems that you can only support slightly more than M connections concurrently. What happens if you want to support 10000 or 100000 concurrent connections? Having that many threads around isn't very efficient. Or what happens if I open M connections and then never send any data. Haven't I effectively DoSed your server?

I think it's better to write the native code "for every platform," since the native code required to do naieve multiplexing is actually portable across almost every platform. What's more, to efficiently support REALLY large numbers of concurrent connections still requires native code even with JDK1.4. Chances are that you'll want to use a platform's advanced I/O strategy when available (like rtsignals, /dev/poll, or kqueues) instead of the standard poll(2) that the JDK calls through to.


--
http://www.thoughtcrime.org
[ Parent ]

Multiple listening threads (none / 0) (#262)
by DodgyGeezer on Tue Nov 19, 2002 at 01:04:26 AM EST

I get the impression this is how IIS is implemented. It's security issues aside, it's fast. I've written some ISAPI extensions in C++. IIS has a pool of threads that handle incoming connections. When the functions in my ISAPI extension are invoked, I'm supposed to return control to IIS as soon as possible... if I don't IIS won't be able to accept anymore connections, which is one reason why it uses multiple threads to listen. This way, IIS always responds to clients very quickly, irrespective of how poorly performing our code is. Incidentally, we just push the incoming requests in to a queue and a single worker thread handles them (although we could easily add more if performance becomes an issue.)

[ Parent ]
DoS and threads v. multiple processes (none / 0) (#351)
by Burning Straw Man on Tue Nov 19, 2002 at 03:19:55 PM EST

In the model you describe, it seems that you can only support slightly more than M connections concurrently. What happens if you want to support 10000 or 100000 concurrent connections? Having that many threads around isn't very efficient. Or what happens if I open M connections and then never send any data. Haven't I effectively DoSed your server?

This isn't any different than a DoS attack against Apache or other forking/preforking server. This is one reason to use THTTPD from Acme, or Boa, instead of Apache, since they use non-blocking IO.

However I do like the architecture you present, with one thread per CPU. It would work well for both IO-heavy applicatoins (like static web server) and CPU-heavy applications (like a render farm).

However, using one thread for "disk IO" does not take into account the possibility of multiple disk arrays. Using one thread for "network IO" does not take into account an array of multitasking network cards. But the next time I am back in < Java 1.4 land, I will definitely be using that kind of architecture.
--
your straw man is on fire...
[ Parent ]

Cooperative Threads ? (4.14 / 7) (#140)
by chbm on Mon Nov 18, 2002 at 11:38:15 AM EST

Oh please. This is simultaneously worse than not having threads at all and than having threads.

While your program executes as if it was single threaded you still need to preocupy yourself with yelding instead of locking. And yet, you can still muck up by yelding in the wrong place which you are bound to do if you can't do locking properly in the first place.
Yeld is just an overgrown handle_events() where you don't actually know where you jump to.


-- if you don't agree reply don't moderate --

Occupying One's Self With Yielding (none / 0) (#269)
by moshez on Tue Nov 19, 2002 at 01:44:52 AM EST

...is good.

I'd prefer to mark the places where my code leaves shared data structures in a consistent state myself, rather than have to mark the places where I'm not leaving the data structures in a consistent place.



[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Project size (none / 0) (#280)
by DodgyGeezer on Tue Nov 19, 2002 at 02:37:27 AM EST

Works well on a single person project, but causes no end of grief when more and more people get involved.

[ Parent ]
Unlike Threads (n/t) (none / 0) (#298)
by moshez on Tue Nov 19, 2002 at 06:46:22 AM EST



[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
No (none / 0) (#324)
by DodgyGeezer on Tue Nov 19, 2002 at 10:26:23 AM EST

I can speak from experience on this: I've worked on a reasonably sized multi-threaded project (17 developers). Threading issues were not the worst problems we had to deal with.

[ Parent ]
Everything is Harmful (3.44 / 9) (#146)
by ph317 on Mon Nov 18, 2002 at 11:52:41 AM EST


The bottom line is that gotos, threads, procedures, functions, variables, memory, processor instructions, and registers are all harmful.  There's about 2^64 ways to screw up software.  Either the programmer is good, and does his best to be correct and efficient (KISS should one of this number one priorities), or the programmer sucks, and he's likely to get bit by his mistakes.  Even the good programmer will turn out bugs from time to time, but he'll be able to resolve them when they come up.  The same good programmer that knows how to use threads also knows more importantly when to use threads.

I think a lot of articles like this one that seem to say "X is bad because programmers often screw it up" are really saying "Most programmers are just hacks who landed job after reading Visual Basic for Dummys - they're not real programmers, so lets dumb down the world of programming to make it easier for them to not screw up".

The real solution to the problem is to hire good coders and give them room to breathe on project timelines so they aren't rushed into bad decisions.  Everything else is duct-tape over a bleeding chest wound.

Not really... (5.00 / 3) (#163)
by Dr. Zowie on Mon Nov 18, 2002 at 01:37:47 PM EST

There is a whole field of interface design that works not just for software but for real-world objects like the control layout of your car and aircraft cockpit design. The fact of the matter is that people have definite limits and you can design systems that work better for people.

Certainly, a good coder can overcome any weird obstacles you throw at her (people actually write useful programs in INTERCAL, for God's sake), but why waste your coder's valuable brain keeping track of minutiae?

To take your argument to a logical extreme, why bother writing in C at all? After all, assembly offers a much more flexible layout for the expert programmer. Come to think of it, since most hardware is Intel, why not just write x86 machine instructions directly, in hex? Why not binary? Symbol tables are for weak-minded fools who shouldn't be coding.

Hmmm... if you accept that some tools (such as a C compiler or a nice set of C++ object classes or, Kibo help us, perl/CPAN) can improve programmer efficiency, then you pretty much have to accept that some constructs are necessarily more helpful and/or harmful than others.

A major source of problems with complex systems is emergent behavior from nonobvious sources. Spaghetti code is harder to keep track of than nice localized code. The point of the article is that threading is like the ultimate spaghetti code.

A really nice reference on this general topic is the (in)famous "big ball of mud" paper -- type "big ball of mud" into Google -- on shantytown programming and why it's so common.

[ Parent ]

An aside on the intro (4.60 / 5) (#147)
by ibid on Mon Nov 18, 2002 at 11:57:29 AM EST

The irony is that Dijkstra didn't use the "considered harmful" theme. It was the (an?) editor (Wirth, if I recall correctly) who retitled the article and moved it in the Letters to the Editor section to speed its publication. Dijkstra's original title was "A case against the Go To statement" (you can find the original manuscript (PDF) in the EWD Archive).

The biggest problem with threads (3.50 / 4) (#148)
by 0xdeadbeef on Mon Nov 18, 2002 at 12:01:49 PM EST

is that there are powerful organizations that promote them as the solution to all problems. God forbid someone implement true IO multiplexing.

Consider this... (2.80 / 5) (#149)
by Fon2d2 on Mon Nov 18, 2002 at 12:04:12 PM EST

Let's say you are writing a chess program to run on a modern graphical operating system such as Windows. Since Windows programs are event driven you need to make each event handler return as soon as possible to the main loop so the user doesn't experience much latency. Unfortunately, there's a problem here: each move your engine makes could take from a fraction of a second to several hours to calculate. Several hours would be too long to spend in an event handler as the program would not be able to respond to user input during that time. You could write the engine in a way that continuously checks for input, but this would be very tricky and error prone, not to mention inefficient, since the engine would have the additional task of polling for input while it is calculating the next move. That would cause an unacceptable performance loss, even if you were clever enough to program it well. Fortunately modern operating systems provide the perfect solution: threads. Now the engine can run in a seperate thread while the interface sleeps. There will be virtually no performance loss and no latency experienced by the user. If this is not the correct way to do things I would certainly like to know.

Well, (4.00 / 1) (#159)
by Kal on Mon Nov 18, 2002 at 12:49:45 PM EST

Alternatively you could use an event based language, like Tcl/Tk, and allow it to handle most of the events for you.

[ Parent ]
Um, Your Design is Horrible (4.50 / 4) (#161)
by moshez on Mon Nov 18, 2002 at 01:26:11 PM EST

Why on earth would I put the chess playing engine in the same process as the chess game server? The correct design for the program above is to have a game server, a bot, and the GUI, all running on one machine. Luckily, all those parts are freely available...you would only need to change to bot's logic, while conforming to the same protocol.

Problem solved. Next?



[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
yes! (5.00 / 1) (#200)
by avdi on Mon Nov 18, 2002 at 05:23:41 PM EST

Why do something efficiently and simply, when you can make it complex, excessively decoupled, and slow!

--
Now leave us, and take your fish with you. - Faramir
[ Parent ]
Modularity, Compartmentalization, Abstraction (NT) (none / 0) (#222)
by kraant on Mon Nov 18, 2002 at 07:20:31 PM EST


--
"kraant, open source guru" -- tumeric
Never In Our Names...
[ Parent ]
What about restraint? (5.00 / 1) (#236)
by avdi on Mon Nov 18, 2002 at 09:09:32 PM EST

Those are all fine things; but the pragmatic programmer also knows how to identify when she's overdesigning/overcoding something based on the given requirements.  In thise case, for a simple (presumably) single-player chess game, a multiprocess, client-server system would be overkill.  Better to code it as a single-process, multithreaded app, and do it in way that's abstracted and modularized enough that it's easy to seperate into components with minimal changes if necessary.

--
Now leave us, and take your fish with you. - Faramir
[ Parent ]
About optimization (none / 0) (#293)
by Quila on Tue Nov 19, 2002 at 05:41:14 AM EST

A computer science professor of mine once gave an assignment for the class to write a certain program that would do some crunching and spit out a result. He guaranteed an A to anyone whose program could run faster than his.

Results ranged from pretty quick to slow, about 20 lines to 50 lines. Everyone used proper coding techniques, etc. His program was one line long and blew everyone else's away, but it was difficult to read.

The moral of the story is that you can use all of your big-sighted architectures and modules for your programs, but a tight-efficient program like the original chess one mentioned will outperform one with a needless client-server architecture. Although debugging may be difficult.

[ Parent ]

Well (4.50 / 2) (#295)
by moshez on Tue Nov 19, 2002 at 06:19:18 AM EST

In this case, the chess playing bot does not benefit from running inside the engine. For a long hard calculation (during which, presumably, it is the only non-sleeping process) nothing task switches, and when it finally reaches an answer, the answer is two byte long (3 bits+3 bits+3 bits+3 bits) which is then transmitted and acted upon. This is exactly the kind of case for which multiple processes are ideal. If you have an SMP machine, with some nice OS configuration for processor affinity, the chess engine will have the ideal conditions for running uninterrupted and giving useful answers.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
But now (none / 0) (#316)
by Quila on Tue Nov 19, 2002 at 09:15:37 AM EST

It won't run any faster in a process than in a thread, and you've just complicated your program. I could see a need for multiple processes in cases, but well-written threads still seem to be the more elegant solution in most cases.

[ Parent ]
Well... (4.50 / 2) (#162)
by joto on Mon Nov 18, 2002 at 01:33:28 PM EST

You could also implement the chess engine as a separate socket. When you think about it, it isn't really much communication that goes on. It is "new game", "move from A3 to A4", and possibly if you want to be more advanced also "abort", "save state to tmpfile.N", restore state from tmpfile.N", etc...

There is little reason to use threads, and the development of the chess engine becomes easier.

On the other hand, when you are using threads, you still need exactly the same communication, only now it becomes interleaved with all the other logic, GUI writing, etc... And you need to worry about thread-safety.

[ Parent ]

Ok, (2.00 / 2) (#164)
by Fon2d2 on Mon Nov 18, 2002 at 01:52:02 PM EST

could you please explain sockets in more detail. How they work and how they equate to an interrupt driven model as opposed to one that uses polling. I am unfamiliar with this technique.

[ Parent ]
Why... (4.00 / 2) (#172)
by joto on Mon Nov 18, 2002 at 02:46:02 PM EST

Why do you not want to use polling? How would you avoid it in the case of threads?

I am not into the win32-api, but at least with pthreads or cthreads, there is no way you could avoid polling even in the case of threads. You cannot do anything interesting inside a signal handler anyway (ok, a rule of thumb, but mostly true).

So, how do you check if there is more data available on a socket? With select(), poll() or whatever equivalent windows has. How do you avoid a system call inside a tight loop? Amortize the cost by only calling the polling function every N times, or if possible, move it out of the inner loop by restructuring the code somewhat.

How would you do it in the multithreaded case? Check the value of some variable? That is polling. There is to my knowledge no other_thread_throw() call in either the win32 api or any other common thread api for altering the execution of another thread in a deterministic manner. Not that it wouldn't be nice, but it simply doesn't exist. Besides, it would still need to use polling internally, to preserve locking invariants, etc...

[ Parent ]

Dude... (none / 0) (#194)
by Fon2d2 on Mon Nov 18, 2002 at 05:00:01 PM EST

Unless there are a lot of interrupts, polling is less efficient. But I've always hated pthreads. They were never set up properly for an object oriented API. It was always much easier to implement threads in a language like Java or through an OOP extension to the Win32 API, such as MFC. But here is why threads are better than polling (yuck, ptooey, I spit on polling). The parent thread, the one that implements the interface here, is not going to execute at all, unless there's an event. That's the point of a sleep state. When there is an event, the OS will wake that thread up the instant (or shortly after) the event arrives, thus the user sees no latency. Meanwhile, the child thread, the one that implements the engine, is free to run full bore, getting very nearly 100% of the CPU time alloted to the process.

You're saying it is an acceptable loss to check for events at known safe spots during the calculation of the next move. You will probably also argue that the loss of polling the event queue is similar to the loss I would have obtaining and releasing locks. Personally I don't think you're escaping any of the issues of design complexity though. Now you have to decide an acceptable trade-off between engine efficiency and user latency and you still have to figure out how to incorporate it safely into your code. See? You don't get away from the real issue of having an interruptable chess engine. You still have to encapsulate your critical sections. I suppose whether you should use threads or not depends on the platform, the architecture, and your own personal preferences. But I think, if you wanted to be portable, and were using a language such as Java, threads would be a good bet.

[ Parent ]

Hey... (none / 0) (#212)
by joto on Mon Nov 18, 2002 at 06:49:31 PM EST

Unless there are a lot of interrupts, polling is less efficient.

Hey, I'm not arguing that. What I'm arguing is that you can't have those interrupts you keep proclaiming. They are a pure fantasy inside your head. They don't exist. It doesn't work that way.

But I've always hated pthreads. They were never set up properly for an object oriented API. It was always much easier to implement threads in a language like Java or through an OOP extension to the Win32 API, such as MFC.

No arguing here. pthreads is intended to be what one builds such user-friendly threads above, not the user-friendly thread-package itself...

The parent thread, the one that implements the interface here, is not going to execute at all, unless there's an event. That's the point of a sleep state. When there is an event, the OS will wake that thread up the instant (or shortly after) the event arrives, thus the user sees no latency.

Yes, this is the way it would work in a single-threaded process as well.

Meanwhile, the child thread, the one that implements the engine, is free to run full bore, getting very nearly 100% of the CPU time alloted to the process.

And if it really was a child process, nothing would change here.

You're saying it is an acceptable loss to check for events at known safe spots during the calculation of the next move.

Not at all. I'm saying you have no choice except to be polling. I would very much like to avoid polling, but unfortunately that is just not possible. Not in pthreads. Not in win32. Not in java.

Personally I don't think you're escaping any of the issues of design complexity though. Now you have to decide an acceptable trade-off between engine efficiency and user latency and you still have to figure out how to incorporate it safely into your code. See?

No. These are decisions that you will have to face, whether you use threads, processes, or a state-machine. It doesn't matter, whatever you do, you will still have to check for commands to the chess engine at periodical intervals. However processes help you alleviate other design problems, that are explained several other places, both in the article, and in comments.

You don't get away from the real issue of having an interruptable chess engine.

In case this is still not clear. You can't have an interruptable chess engine. I dare you. Write an interruptible chess engine and prove me wrong. You will realize that you were preaching out of your ass. Show me a way of communicating "move A3 to A4" to the chess engine that does not involve some kind of polling.

I suppose whether you should use threads or not depends on the platform, the architecture, and your own personal preferences.

Well, with those kinds of considerations taken into account, that can be said of pretty much anything.

But I think, if you wanted to be portable, and were using a language such as Java, threads would be a good bet.

If you wanted to be portable, threads would be a bad bet. If you were using Java, threads would be the only way to be portable, but Java is pretty strange in that respect.

[ Parent ]

Long Waits in Windows (none / 0) (#239)
by Surly on Mon Nov 18, 2002 at 09:24:22 PM EST

In your situation, I would fire off a thread to do the work and then use the MsgWaitForMultipleObjects to wait on the thread to complete while still dispatching windows messages.  

My personal belief is that threads should not be attempted until you have a firm grasp of programming, and then only with a mentor's help.  (The same goes for multiple inheritance).

[ Parent ]

One solution (3.50 / 2) (#302)
by Znork on Tue Nov 19, 2002 at 07:08:01 AM EST

Apart from other suggestions like decoupling the engine entirely and running it over network (which would make sense if you're writing a multiplayer chessgame), a chess engine would also be very easy to implement so that it calculates one evaluation per main event loop (or more, since you'd probably have several thousands if not millions of move evaluations per second).

You dont need any input logic in the actual engine, you just need to write the engine so it returns often enough. Not very tricky, nor very inefficient (something like one return to main event loop for input per 1000 evaluated moves would be negligable).

[ Parent ]

To thread or not to thread (4.00 / 5) (#150)
by jd on Mon Nov 18, 2002 at 12:07:34 PM EST

First, the original article misses the entire essence of parallel programming. If the processes are indeed parallel in nature, you JUST DON'T CARE where one is, in relation to the other.

This is one of the major reasons parallel programming is considered "so terribly difficult". Things happen out of sync, and they're supposed to! ARGH!

The philosopher's dining problem is a classic example of what happens when you use sequential logic on a parallel problem. The dining philosophers can't eat, because each is waiting for the others. Sequential logic. Since eating is not a function dependent upon the state of any of the others, any (or all) can eat with total disregard for the state of the other philosophers.

In the real-world, this would look something like this: Let's say you have process A and process B. A needs output from B to resolve specific variables, but doesn't actually need to check the values until much later on. B, likewise, depends on output from A, but DOES need to resolve immediately.

A hits the point where it reads from B. Nothing there. A spawns off a watcher, to monitor for that input, and continues. The values are only partially evaluated, but that doesn't matter because we don't need the actual value at that point.

B hits the point it wants input from A. B doesn't find any. There are two cases here. B either has sufficient information to guarantee a result will be completely inside or completely outside the bounds of interest. In this case, if the data is not subsequently used, it can just drain that input when it does arrive, and ignore it completely. The other case, where the results might result in going down one of N tracks, B is probably best to spawn a watcher, again, and fork down EVERY ONE of those paths. When the watcher collects the data, B can eliminate the redundant sub-threads.

What does this buy us? Well, it eliminates deadlocks completely. Instead of waiting for one definite path, we try all possible paths. "But what about reads/writes?" You do delayed resolving, again.

Let's say that B writes something to a file. What that something is will depend on which of those N paths it has taken. A performs a read operation. Now, we still don't know which path B has actually taken, at this point. But why should we care? A is guaranteed input by the time it needs to perform any definite resolution, but until then, it just needs to know that input exists and the operations that will be performed upon it.

"This sounds too complicated!" It's an inevitable consequence of deadlock-free parallelism. You HAVE to introduce probabilistic paths, with resolution on observation. Basically, "quantum computing", or at least, a sizable chunk of it.

(This leads me on to one other point. Since parallel logic can emulate quantum uncertainty, in this way, and since parallel logic is "computable", quantum uncertainty is also "computable". This means that quantum computers will be ONLY capable of performing those tasks that "ordinary" computers can perform. The sole difference will be one of performance. The range of solutions available will be identical.)

You make my head spin. (none / 0) (#157)
by Fon2d2 on Mon Nov 18, 2002 at 12:19:34 PM EST

In my Operating Systems course we had this teaching assistant that could like verify your programs just by looking at them. So you'd write some program to simulate producers and consumers or some similar modern problem, spend all this time running it to make sure there were no bugs, and then hand in only the printout. Next week you'd get it back with red ink around some semaphore or some critical section saying there was a race condition in your implementation. Sure enough he was right.

[ Parent ]
Re: You make my head spin. (5.00 / 2) (#173)
by GGardner on Mon Nov 18, 2002 at 03:07:48 PM EST

In my Operating Systems course we had this teaching assistant that could like verify your programs just by looking at them

I was that TA. At least in my case, my godlike powers of program verification came because I looked at the same broken progams again and again every week, often broken in the same way. It's amazing how quickly you can spot a bug the fourth time you've seen it in a week.

[ Parent ]

To pseudo or not to pseudo (none / 0) (#190)
by joto on Mon Nov 18, 2002 at 04:40:21 PM EST

First, the original article misses the entire essence of parallel programming. If the processes are indeed parallel in nature, you JUST DON'T CARE where one is, in relation to the other.

Well, duh. Parallel processes are parallel, I didn't know that! It's good that you decided to enlighten us with that essence.

This is one of the major reasons parallel programming is considered "so terribly difficult". Things happen out of sync, and they're supposed to! ARGH!

Oh, yes. Now I understand! Thanks for your explanation. It really helped!

The philosopher's dining problem is a classic example of what happens when you use sequential logic on a parallel problem. The dining philosophers can't eat, because each is waiting for the others. Sequential logic.

No. It is a classic example of what happens when several philosophers (processes) compete for limited resources (forks). If you find any "sequential logic" in it, it's your brain who is at fault, not mine.

Since eating is not a function dependent upon the state of any of the others, any (or all) can eat with total disregard for the state of the other philosophers.

But eating is a function dependent upon the state of the others. There is not enough forks for all to eat at the same time. Of course, you may redefine the problem to your own liking, but then it would be unfair to still call it the dining philosophers.

In the real-world, this would look something like this: [blurb deleted]

Depending on view, what you have said, it either doesn't make sense at all (you contradicted yourself several times throughout the piece), or can be interpreted as follow (and now I'm being nice): "Instead of letting a thread wait for data, we speculatively execute it for every possible value of the data it should read.".

This is of course pure and utter nonsense (and neither does it solve the deadlock problem, unless you can guarantee that something useful will eventually come out of all that speculative execution, but I can pretty much guarantee you that it will not).

You may babble about quantum computations all you want (and I'm not claiming to have a full grasp of that), but in the context of paralell programming, words just fail to describe the stupidity of your suggestion.

And while your conclusion that quantum programming will only differ from normal programming in performance (not ability) is correct, your reasoning is about as valid as that found in a certain monty python sketch involving a duck and a witch.

[ Parent ]

Co-operative multithreading... (3.00 / 6) (#151)
by Fon2d2 on Mon Nov 18, 2002 at 12:12:32 PM EST

BWAHAHAHAHAHAHAHAHAHAHAHAHA

Dataflow programming (4.50 / 4) (#152)
by unDees on Mon Nov 18, 2002 at 12:12:50 PM EST

What about dataflow programming in languages like LabVIEW, where you get threading essentially for free, and a lot of worries about deadlocks and synchronization are shifted to the shoulders of the folks who wrote LabVIEW?

Granted, you still have to decide what to do when multiple threads need to share resources or data. But it's a lot simpler in the dataflow world. The most common errors I've seen have been race conditions, not deadlocks or mysterious crashes under load.

Your account balance is $0.02; to continue receiving our quality opinions, please remit payment as soon as possible.

Sounds good to me. (5.00 / 1) (#167)
by joto on Mon Nov 18, 2002 at 02:09:42 PM EST

Well, if LabVIEW has mostly solved the issue of threads (which seems reasonable in a dataflow language), then all the power to those using it!

I've never actually used a dataflow language, but it seems (from the height of about 10000 meters, where I look at it) like a neat idea that in many cases would be much more sane than doing OO. I'm not sure how you can scale that graphical programming to larger projects, though...

I could imagine that if I were an actual engineer instead of one mostly focusing on software problems, LabVIEW would be very fun to play with.

[ Parent ]

LabView (none / 0) (#333)
by katie on Tue Nov 19, 2002 at 11:31:44 AM EST

It's fine as long the problem you want to solve is nice and easily expressible as a dataflow problem.

Unfortunately, my experience with it was watching a real-time engine control system being built in it. That was painful even from quite a long way off...

[ Parent ]

Tools and problems (none / 0) (#344)
by unDees on Tue Nov 19, 2002 at 01:56:03 PM EST

Well said--indeed, I think you could make that claim of any tool: "It's fine as long as the problem you want to solve is nice and easily [solvable by that tool]." I've seen several projects for which LabVIEW is well-suited. And there are undoubtedly many tasks for which it's not.

I'll defer to your judgment call on that particular real-time system, but I know of several people that have joyfully and skillfully built other real-time control systems in LabVIEW.

Your account balance is $0.02; to continue receiving our quality opinions, please remit payment as soon as possible.
[ Parent ]

I like LabView (none / 0) (#206)
by epepke on Mon Nov 18, 2002 at 06:15:16 PM EST

I've only used it for programming Lego robots, though.

In a dataflow language, the topology of the dataflow network is described explicitly rather than implicitly as in most programming tasks in essentially linear languages. Therefore, the system itself can determine when and how to do multithreading and how to avoid the problems with it. This is pretty safe.


The truth may be out there, but lies are inside your head.--Terry Pratchett


[ Parent ]
Fun with threads (Heisenbugs) (4.20 / 5) (#155)
by czth on Mon Nov 18, 2002 at 12:16:53 PM EST

In this course, part of the OS assignment was to implement multiprocessing. I guess some groups' implementations were a little leaky (or their locks didn't), because they ended up stuffing their program with printf("")s, which "magically" caused their program not to crash... well, until the next spot where a process switch occurred at a bad time, anyway.

(Incidentally, not only did I implement processes, but I also implemented threads, since, as another comment says, the only difference is the sharing of data; processes share code, threads share code and data, and if you find something that shares code, data, and stack, I don't want to hear about it. Having threads gave me several big wins over the other groups (who had to do a lot more IPC), and since I listened in my concurrency course and have a fair amount of clue, I didn't screw up.)

Ah, memories of realtime....

I used to think that anyone could learn anything if they put their mind to it. I don't know if I still believe that, especially after seeing certain peoples' code. Maybe there's a "threads gene" sort of like Joel's "pointers gene", and some people just don't have it, and as long as they go on to be businesspeople or artists or waitresses or truck drivers or pilots, that's fine, but heaven forbid they try to become programmers.

czth

Sharing code, data, and stack - Multitasking+GOTO (none / 0) (#183)
by cribeiro on Mon Nov 18, 2002 at 04:12:23 PM EST

If you add GOTOs to a threaded environment, you get stack sharing. It's a terminal nightmare - the closest to chaos you can possibly get with programming (can you imagine, QBASIC+ - QBASIC with Threads?).

[ Parent ]
Nope (none / 0) (#232)
by sholden on Mon Nov 18, 2002 at 08:07:22 PM EST

Threads don't share stack, adding gotos to code doesn't magically cause threads to share stack, it causes control flow to go to hell...

The befunge that befungers tried to forget, Befunge-96, has shared stacks. Since befunge is a stack machine that's about as bad as it gets :)

My references to it are now 404s but http://www.mines.edu/students/b/bolmstea/mtfi/96.html is found by google.

--
The world's dullest web page


[ Parent ]
Mathematical Errors Considered...well, you know (3.66 / 3) (#160)
by KWillets on Mon Nov 18, 2002 at 01:25:12 PM EST


threads will only spend 0.01% of their time (perhaps underestimating needed memory for some buffer). Now, line 524 is really simple code: threads will only spend 0.01% of their time there. If there are two threads, it is a one-to-ten-thousand shot that they will be there at the same time.

p = .01%  = .0001 = 10^-4

p^2 = 10^-8, or one in 10^8 = 100,000,000.

Calculations Correct (3.75 / 4) (#185)
by moshez on Mon Nov 18, 2002 at 04:23:30 PM EST

But irrelevant. Explanation left as an excercise to the reader.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Explanation (4.00 / 2) (#244)
by Repton on Mon Nov 18, 2002 at 09:39:41 PM EST

Okay, I'll bite ...

There are two threads. Freeze the program. The chance that each thread is in line 524 is 10-4, so the chance they are both there at this particular time is 10-8.

But we know that the threads do use that line. So choose a point in time when thread A is in line 524. What is the chance that thread B is also there? 10-4 ...

(equivalent problem: What is the chance that two people have the same birthday? It's not (1/365)2)

--
Repton.
They say that only an experienced wizard can do the tengu shuffle..
[ Parent ]

Semaphores Considered Harmful (2.80 / 5) (#177)
by pridkett on Mon Nov 18, 2002 at 03:42:07 PM EST

I have to say that this seems to be one of the most factually inaccurate articles I have ever seen on K5. I see this article as being a little bit like "assembly language consider harmful" or "kernel drivers considered harmful", the simple answer is of course they can be harmful. But what your "paper" is more about is semaphore locks being harmful.

As any CS student can tell you, semaphores, much like C, give you the rope to hang yourself pretty easily. Their inherent low levelness requires thoughtful usage, and, gasp, design (of which most students in my operating system class have little idea of).

So then seeing that semaphores worked well enough, but only if you actually thought about the implementation, there was this little thing called a monitor invented. In a monitor you only get access to the elements of data that you need and only when multiple conditions are met. It's wonderful and some languages even do an OK job of implementing them. While they don't solve every problem with synchronization (you still can forget wait and signals and what not) they don't suffer from quite as many problems as semaphores. ADA even goes a bit further with protected types.

So while the "paper" did a good job of showing why semaphores are harmful, I'm still left wondering why threads are harmful. What alternatives are there to threads, well, the most common is multiprocess (heavy weight processes here). These take a longer amount of time to create, but can be scheduled independently. However, you still need a way to communicate between each other. And do you really need a full copy of mozilla, data segment and all, to handle a status bar?
--
Read this story.

It does look a bit like that... (3.00 / 1) (#193)
by pb on Mon Nov 18, 2002 at 04:53:23 PM EST

Actually, as the author mentions, this is "considered harmful" in the same way that "Goto Considered Harmful" is, and it seems to have gotten the same reactions despite the fact that this was all mentioned up front.

That is to say, sometimes a goto statment--like threads--is the right tool for the job.  And 90% of the time it isn't and people do it anyhow.  I think this article helps explain some of the common pitfalls people run into when using threads, as well as explaining when you might not want to use them.

As for semaphores, well, that is the basic locking primitive that people use to protect their threads--like it or not, that's what people learn, use, and mess up.  And unless by "some languages" you meant to say C or C++, I think a lot of people won't be using monitors.

Obviously you'd have to evaluate for yourself whether it's more appropriate to share or copy your data.  But many OSes have their Copy-On-Write semantics implemented correctly, which negates much of your argument about needing a full copy of mozilla to handle a status bar--you don't, whether you're using processes or threads.
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]

semephores? (none / 0) (#199)
by avdi on Mon Nov 18, 2002 at 05:22:25 PM EST

As far as my on-the-job C/C++ multithreaded education went, semaphores were a lately learned, low-level curiousity.  Long since superseded by higher-level constructs.  If what you say is true of most university CS courses, no wonder the kids are scared of threads.

--
Now leave us, and take your fish with you. - Faramir
[ Parent ]
So what did you learn then? (none / 0) (#231)
by joto on Mon Nov 18, 2002 at 08:04:39 PM EST



[ Parent ]
Lessee... (none / 0) (#237)
by avdi on Mon Nov 18, 2002 at 09:17:33 PM EST

Simple and read/write mutexes; recursive mutexes/tokens; barriers; condition variables; exception-safe scoped-locks, threadsafe queues - those were the primitives.  Reactors, Proactors, Polymorphic Futures, tasks, active objects, atomic-op classes, acceptor/connectors - those are some of the higher-level patterns that built on them.  To be sure, somewhere in the process I learned about semaphores but except for certain specific uses for which they're suited theu don't see any direct use.  They'er too low-level.

--
Now leave us, and take your fish with you. - Faramir
[ Parent ]
Threads and monitors (5.00 / 2) (#202)
by joto on Mon Nov 18, 2002 at 05:29:02 PM EST

It's wonderful and some languages even do an OK job of implementing them.

Please tell me about one. I'm not aware of any language that do an OK job of implementing monitors. Well, now that I think of it, maybe something like AspectJ would do, but I guess you had something more tangible in mind?

But in reality, it's not just semaphores (or mutexes) thare are harmful. It's also threads by themselves. Because the moment you have threads, you will also have to worry about thread-safety.

But even that isn't enough. Because there really is something inherently wrong with threads. They default to sharing. This is just like global variables (evil) compared to block-structure (good). Sure, you can do useful work with them, but that doesn't mean the fundamental approach is in any way right.

It is wrong from an aesthetic viewpoint, and it is wrong from a performance standpoint (unless you limit yourself to only worry about those machines having 1..16 processors, where message-passing is not necessary).

[ Parent ]

Threads Considered Powerful (4.22 / 9) (#178)
by avdi on Mon Nov 18, 2002 at 03:50:07 PM EST

And on and on it goes... until this generation of aging hackers moves on to better things, they will forever be harping on those awful, scary threads and how dangerous they are.  I've observed (and confirmed through some very informal polling) that the cause of the FUD surrounding threads stems primarily from two sources:  the UNIX mindset, and scripting languages.

First, the UNIX mindset.  Understand that I love UNIX, and years ago converted to an all-Linux household.  It's my favorite development environment, and the home of THE standard threading interface, pthreads.  However, UNIX programers have tradtitionally lagged behind the OS/2, BeOS, and WindowsXX  communities in their grasp of threaded programming because that tremendous kludge, fork-and-exec, has so long dominated UNIX coding.  Introduced, IIRC, as a clever kludge based on the fact that certain early UNIX machines could duplicate a process very quickly, many UNIX programmers have gotten it into their skulls that fork-and-exec is an elegant and powerful idiom, suitable for anything threads are good for.  It's neither, but good luck convincing a longtime UNIX hacker of that.  The idea that you would want to duplicate an entire process, with all the painful IPC overhead that entails and the ugly fact of having a single piece of code do two completely different tasks based on the return of the fork() call is absurd on it's face.  Many coders still think otherwise however.

Second is the fact that most of the recently popular scripting languages have innefficient, broken, or eternally-beta threading models (coughPerlcough).  And the interfaces are generally very primitive, much closer to the OS interface than, say, their IO interfaces are.  For this reason hackers get the idea that threads are "hard", that they are the domain of wizards and not mere mortals.  

And that's really the biggest problem, the perception that threads are hard, that they are easy to get wrong and that they cause cancer if misused.  Which is true (except the cancer part) - if you are still using primitive threading interfaces and ancient idioms.  Modern software engineering practices and patterns eliminate much of the pain and uncertainty of coding with threads.  If you think threads are mysterious and hard, read up on the Doug Schmidt's ACE libraries.  Read some of his books. Threads have come a long way.  Experienced thread programmers use the Reactor pattern to synchronously handle aysnchronous events.  Recursive mutexes eliminate one source of deadlocks.  Active objects encapsulating their own threads of control, sending commands and requests to each other via threadsafe queues, are preferred to primitive spaghetti-code threading.  

Another, lesser misconception is that threads are somehow not applicable to real-world problems.  This just is silly.  Embedded programming of any complexity is almost exclusively multithreaded.  The millions of computers and embedded systems used in aerospace systems, in communications and industrial control systems, and dozens of other mission-critical fields almost invariably require fast, reliable multithreading.  One cause of the failure to percieve threading's relevence is probably just the fact that these systems, while ubiquitous, are unglamorous and nearly invisible to the average database, internet, or desktop developer.  Whether you realize it or not, you entrust your life to multithreaded systems all the time.

Ironically, in an article dissing threads, the author actually recommends one of the Best Practices in thread programming: an event-based model.  Why this is percieved as at odds with using threads is beyond me;  experienced multithreaded programmers will usually do their best to design an event-based system, as these tend to be more robust and easier to comprehend.  In general, you can't have [high level] events without threads to generate them.

In conclusion:  threads are not scary.  Here be no dragons.  I recommend to anyone who thinks thay are frightening or dangerous that they shake off their superstition, read some good, modern books on the subject, and dive in.

This message has been brought to you by the Thread Council ;-)

--
Now leave us, and take your fish with you. - Faramir

ageing unix hackers... (4.33 / 3) (#207)
by joto on Mon Nov 18, 2002 at 06:21:42 PM EST

...they will forever be harping on those awful, scary threads and how dangerous they are.

I have an immense respect for the ageing unix hackers I've met. And it would take a lot to make me think someone describing them in derogatory terms would actually have something to contribute.

stems primarily from two sources: the UNIX mindset, and scripting languages.

Probably true. And threads popularity mainly come from Microsoft Windows and Java. So what.

because that tremendous kludge, fork-and-exec, has so long dominated UNIX coding.

Ahh, fork()/exec(), one of the single most useful features of unix is at fault. So because fork()/exec() is so enormously powerful and useful, it is now put to blame for people not reverting to more mainstream but arguably worse alternatives? Personally, I think the designers of that model should feel pretty proud by such a statement.

[fork()/exec()]suitable for anything threads are good for. It's neither, but good luck convincing a longtime UNIX hacker of that.

Yes, good luck. Most unix hackers actually understands these issues, and the tradeoffs involved. And that's why we will not be lured to the evil side by some dumbass with convincing rhetoric but whose actual arguments revert to mainly name-calling.

idea that you would want to duplicate an entire process, with all the painful IPC overhead that entails and the ugly fact of having a single piece of code do two completely different tasks based on the return of the fork() call is absurd on it's face.

Nobody claims that it isn't absurd on it's face. But looks deceive. And when you get to know it, it is one of the most useful features unix offers.

scripting languages have innefficient, broken, or eternally-beta threading models (coughPerlcough).

You would be surprised how few actually see that as an argument of anything else than to avoid threading in those languages.

Modern software engineering practices and patterns eliminate much of the pain and uncertainty of coding with threads.

Or, as I would say; CORBA and Java forces us to use threads even where they aren't needed. With all the negative side-effects that has, including performance, stability, etc...

Experienced thread programmers use the Reactor pattern to synchronously handle aysnchronous events.

Ahh, exactly. Avoid threads if you can!

Active objects encapsulating their own threads of control, sending commands and requests to each other via threadsafe queues, are preferred to primitive spaghetti-code threading.

Hmm, it almost sounds like programming for... tata: VxWorks. So what you are saying is that win32 programming has now reached the level of sophistication where it is almost as easy as programming an embedded OS?

Another, lesser misconception is that threads are somehow not applicable to real-world problems.

I've yet to find a person with that misconception, and don't think I ever will, but ok, our experiences might be different.

In general, you can't have [high level] events without threads to generate them.

Yes, you can. And I do it all the time.

In conclusion: threads are not scary.

And neither do I nor anyone I know find them particulary scary. I do however consider them a very bad idea. We (and as you point out, especially in the unix world) have better alternatives.

In conclusion: I do not know where your kind gets the idea that just because someone prefers something that is arguably better, one has to be a moron and not understand the worse alternative. If I had said I was against goto, would you have said that modern programming techniques handles goto's much better than in the 60's, point me to some books about someone doing useful work with goto, and claim that there is no reason to be scared of goto?

[ Parent ]

My response (none / 0) (#246)
by avdi on Mon Nov 18, 2002 at 10:14:26 PM EST

I have an immense respect for the ageing unix hackers I've met. And it would take a lot to make me think someone describing them in derogatory terms would actually have something to contribute.

I have an emmense respect for the embedded coders I work with.  Who, like most embedded coders, have been working with threads for decades. This tends to come as a shock to people who, say, think that Java and Windows brought threading to popularity.  If Java and Win32 is all that the word "threads" brings to mind, you know nothing of what you speak.

I also have an immense respect for aging UNIX hackers.  More on that later.

Or, as I would say; CORBA and Java forces us to use threads even where they aren't needed. With all the negative side-effects that has, including performance, stability, etc...

What have CORBA and Java have to do with this discussion?  CORBA's more often used for IPC; it's massive overkill for multithreaded applications.  And Java is a somewhat backward newcomer.  It sounds like you are just tossing off buzzwords that you seem to recall being vaguely related to multithreaded programming.

Hmm, it almost sounds like programming for... tata: VxWorks. So what you are saying is that win32 programming has now reached the level of sophistication where it is almost as easy as programming an embedded OS?

And thankfully, finally, so has UNIX.  Why exactly do you bring up one of the popular platforms for multithreaded programming in defense of your anti-thread position?  Although by comparing my statement with VxWorks, you've again shown your ignorance of the patterns I referred to.  I'm familiar with the OS, and the patterns I'm talking about are at the next higher layer of abstraction, at least.  

Oh, and what is with your obsession with Win32?  Do you honestly believe it's at the center of threaded programming work?

For all that you accuse me of being "derogatory", you are the only person in this conversation calling anyone stupid.  I did not and do not consider most aging UNIX hackers stupid; I never said anything of the sort.  I consider them, by and large, to be a shining light to young, inexperienced programmers.  However, like most aging artisans, they tend to be rather set in their ways; and they understandably refuse to move past an dated  model, based on the idiosynchrousies of long-retired hardware, that they are familiar with.  I don't grudge them this, except for the fact that the Linux craze (of which I admit I am probably a member) has driven a lot of young coders bright-eyed and innocent into the arms of these wise old men and women, and some of them have been scared off by tales of ancient dragons long since vanquished.  The old UNIX hackers also tend to pass on that distinctly myopic viewpoint that you seem to share, that the sort of problems that big-server UNIX-style programming is good for are the only important and interesting problems in the world.  The blindspot that the UNIX world (and most of the Windows desktop world as well) has towards the embedded, aerospace, and telecom fields is amazing to me.

I do not know where your kind gets the idea that just because someone prefers something that is arguably better, one has to be a moron and not understand the worse alternative.

Which sums up your attitude well.  You can't imagine that someone with different opinions from you is actually a productive and experienced programmer, using the proven best practices in his field.  Instead, we are some other, lesser species - and we must perforce consider "your kind" to be morons because of it.

You know what?  I'm a professional in my field.  So, for all I know, are you.  I'm not talking out of my ass, and I assume neither are you; but you are the only one here who thinks there is some kind of inter-species rivalry going on and feels it necessary to resort to petty insults about intelligence.  If I were prone to that kind of thinking, I suppose I could take it as a confirmation that "your kind" are all good-for-nothing idiots; but I think rather than drawing with that broad brush I will maintain my high respect for UNIX programmers.  And I'll go on solving problems in ways that are fast, elegant, and safe - and learning new techniques  from people who have differing experience but enjoy sharing knowledge rather than bandying insults.

--
Now leave us, and take your fish with you. - Faramir
[ Parent ]

You are right, I was talking out of my ass... (4.00 / 5) (#291)
by joto on Tue Nov 19, 2002 at 04:53:02 AM EST

You can't imagine that someone with different opinions from you is actually a productive and experienced programmer, using the proven best practices in his field. Instead, we are some other, lesser species - and we must perforce consider "your kind" to be morons because of it.

Yes, I can imagine that. In fact, most people with differing opinions about technical subjects are productive and experienced programmers. Compare with vi-emacs. However, in the case of threads, most people think threads are good, before they really know any thing about it, or it's alternatives. This comes mainly from Sun/Microsoft marketing, and not at all from actual experience, or reasoned opinion.

Among the "thread-likers", I would say that the majority is of that type. You, who are actually informed, reasoned, reasonable, etc... are in a strong minority, but your views are well respected by me.

It is obvious you come from a very different culture than me. From my rereading of your comment, it seems you have very similar views of "thread-haters". That the majority of us have no idea how to use threads, how to use commonplace patterns, etc... That is possibly true. It was however, an invitation for me to flame you, and so I did (not that that is an excuse, but you shouldn't really be that surprised).

What have CORBA and Java have to do with this discussion? [snip] Why exactly do you bring up one of the popular platforms for multithreaded programming in defense of your anti-thread position? [snip] Oh, and what is with your obsession with Win32? Do you honestly believe it's at the center of threaded programming work?

As explained above. I clearly missed the target.

For all that you accuse me of being "derogatory", you are the only person in this conversation calling anyone stupid.

No, I haven't called anyone stupid. I might have called you a dumbass, though (sorry:-)

It was never intended to convey anything about your level of intelligence, but towards your behaviour. You seemed to assume that intelligent people with experience should simply start using threads everywhere, because "more modern" techniques now made one of the most practical features of unix "obsolete", and besides: you cried louder and whined more often than them.

I realize now that this was not what you tried to say, but it was how I read it. And after rereading your comment, it is still what I read from it.

[ Parent ]

Some clarifications (5.00 / 1) (#326)
by avdi on Tue Nov 19, 2002 at 10:46:06 AM EST

Let me just say that I did expect fairly vehement responses to my original post.  I'm an opinionated hacker and I let my opinions show through in that one.  I do make a point of drawing a line between my opinion of certain attitudes and practices, and of the people who hold the opinions and practice the... um... practices.  I think the stubborn ignorance of fields outside their own that some hackers hold to is stupid, and I'm not afraid to say it.  I also think that while multiple processes are the best solution to a (probably large) set of problems, fork-and-exec is still a wierd, counter-intuitive, and over-coupled way of starting those processes.  I try to make it clear that it's the attitudes and practices, not the programmers themselves, which deride.   I can also be shown that a practice or attitude has merit, and when I am, I promptly cease to insult it.

What I object to, as I said, in UNIX hacker culture is not their intelligence, of which they have more than their share;  it's a certain widespread myopia that seems inconsistent with their generally fiercely intelligent and curious natures.  It reminds me of those "A New Yorker's View of the World" posters, where Manhattan Island is this vast continent, other states are tiny fiefdoms, and the rest of the world is a collection of insignifigant little islands.  It's not limited to UNIX hackers of course;  there are web coders who can't understand why anything would need to be coded at a lower level than Perl, and Win32 desktop programmers who think that UNIX is some archaic system that no one uses anymore.  It's just that I expect better of the UNIX-heads, who after all have been driving a lot of the industry for the last thirty years at least.

Basically, I object to attitudes such as those shown in the article, where the poster clearly has  little practical experience in the fields where threaded programming is important, has had a hard time grasping it himself, and therefore writes that it's a bad idea.  It's an attitude that's unfortunately likely to draw a lot of knee-jerk acknowledgement in UNIX-land, which does no one anygood.  And yes, it's also likely to draw a lot of knee-jerk derision from wet-behind-the-ears Win32 programmers, which also does no one any good.  K5, and the hacker community in general, needs less "X considered harmful", and more "X considered useful for doing Y, and here's why and how to do it right".

BTW, I would be quite interested in a justification for the modern usage of fork-and-exec as a way of spawning processes.  Is it simply the fact that it's easier to coordinate whatever channels they use to communicate if they start out as the same process?

--
Now leave us, and take your fish with you. - Faramir
[ Parent ]

P.S. (none / 0) (#327)
by avdi on Tue Nov 19, 2002 at 10:48:41 AM EST

It occurred to me that I may be using "fork" and "fork-and-exec" interchangably where I should not be.  Correct me if I'm wrong.

--
Now leave us, and take your fish with you. - Faramir
[ Parent ]
Cool, let's bury the axe then... (4.00 / 1) (#346)
by joto on Tue Nov 19, 2002 at 02:35:15 PM EST

t's a certain widespread myopia that seems inconsistent with their generally fiercely intelligent and curious natures.

Probably true. I wish it weren't, but you have probably hit the nail on the head here...

By the way. I really enjoyed reading some of the patterns on the ACE. site But tell me, why is double-checked locking still on the list? It is now quite well-known that it doesn't work with an optimizing compiler or smart memory unit...

BTW, I would be quite interested in a justification for the modern usage of fork-and-exec as a way of spawning processes. Is it simply the fact that it's easier to coordinate whatever channels they use to communicate if they start out as the same process?

That is certainly one of the more important reasons. Between fork() and exec() you are allowed to do a lot of useful things that will be kept in the new child image. This includes things like open file-descriptors (which can be sockets, pipes, open files, serial ports, virtual terminals, or generally anything you like). You can set the user and group id of the child, resource limits on various system resources, signal masks, pending signals, scheduling class and priority, working directory, root directory, environment variables, controlling terminal, umask, time left until next alarm(), file-locks, and some other less useful things. Also, processes are organized in a hierarchy where each running process has a parent process, and they can be set up to be killed whenever the parent dies.

Needless to say, this gives you enormous flexibility to reuse existing programs for things they were never intended for. An example of this is e.g. the inetd super-server that basically listens on lots of sockets, and launch very simple "servers" that read from stdin, and write to stdout. If you want to, you can set up inetd to launch a sort server simply by asking it to run the sort program when someone connects to port X. Another example would be the plumbing that goes on in the unix command-line. But this is only the beginning of what this flexibility will buy you. It is also very helpful in building and managing large complex systems, running hundreds of processes.

The ability to not call exec() is also important. It means that when two processes are intended to be cooperating, they can live in the same process-image (paged memory, copy-on-write), and thus save system resources instead of actually having to set up a new process with exec(). When you are not running exec(), you also keep any other parameters, such as shared memory, message queues, semaphores, etc, making it a very potent alternative to threads in the large majority of cases.

In many ways fork() is the opposite of spawn(). With spawn(), the designers thought that you really didn't want any control of your children, and if you ever called spawn(), it was to launch an entirely different program. With fork(), the assumption is the opposite. The designers thought that in most cases, when people used fork(), it was to set up a helper-process for the parent, and the parent should be able to set up lots of parameters before or if a new image was loaded, and also to be notified if the child for some reason exits or crashes. This results in a lot of flexibility that can mostly be ignored if you don't need it, but is very useful when you do.

As for fork() being unintuitive. Yes, it probably is. But it my opinion it doesn't matter. How many things in programming are really intuitive anyway? Of course, you could say something like e.g. pthread_create(), where the new thread is given a start function is more logical than fork() that returns a value to indicate whether you are parent or child. And you may be right (I don't think so, but I'm probably brainwashed). Feel free to write a avdi_fork() that does that, then. All you have to do is check the return value, and call that function, and you have the same functionality. For extra points, you can longjmp to the bottom of the stack before you call the user-supplied function.

As for fork() being inefficient, this is simply wrong. Sure, you may be able to save a few cycles with a direct spawn(), but not much. Mostly you will save one system call, and one page-fault. The page-fault is for the top of the stack, if you happen to write any variables there before you exec(). And yes, maybe some TLB fiddling by the OS could be saved as well. But you would probably have to spawn a lot of processes to measure any real difference. The time used by e.g. the dynamic loader is probably much greater. And in return, you get something that is so much more flexible. If you want spawn(): write it as fork() || exec("...") for obscurity and minimal error checking:-)

[ Parent ]

The real problem with threads (none / 0) (#208)
by Scott Marlowe on Mon Nov 18, 2002 at 06:23:44 PM EST

Is a lack of a portable environment from one platform to another.  On the Postgresql hackers mailing list someone pops up every three months or so and asks when the postgresql backend will become multi-threaded, and the same answer comes back, it won't.  For one, only certain portions of the backend would benefit from it, but more importantly, to make sure your database works on Linux, BSD, HPUX, Solaris, AIX, SCO, Windows Cygwin, Windows Native (coming soon to a theatre near you) etc. etc. requires a reliable, portable thread library.  

Right now, it seems no single thread implementation works well across all OSes, in terms of both reliability and having good performance (you can have one it seems, but not both.)

Finally, I would postulate that for every old time unix hand who thinks threads are more dangerous than a teenager with a credit card, there's a brand new CS degree holding programmer who thinks they're the cure for world hunger.  I'm sure the answer lies somewhere in between, but I'd honestly have the old, cautious unix hand writing code for my database than the shiny new programmer...

[ Parent ]

Have the Postgres people looked at ACE? (none / 0) (#241)
by avdi on Mon Nov 18, 2002 at 09:27:20 PM EST

I hesitate to say that it would solve their problem, because I don't know enough about it.  But ACE compiles natively on all those platforms (and many more); and one of the wonderful things about it is that even if you don't use the higher-level abstractions, you can still use the wrappers to do multithreaded and/or network programming without worrying about portability issues.  This isn't just hype; we rely on this property of ACE at my company.  It's quite remarkable the first time you see a multithreaded program written completely on UNIX compile and run perfectly on Windows, or vice-versa, all because of ACE.  There's no reliance on Cygwin or anything like that - it uses native OS calls on Solaris, HPUX, Linux, BSD, QNX, VxWorks, WinNT, Win2K, WinCE... the list goes on and on.  It's mature and it *really works*, as I can attest to first-hand.  A lot of C++ programmers consider it to be that de-facto "standard" threading API that you bemoan the lack of.

--
Now leave us, and take your fish with you. - Faramir
[ Parent ]
ACE (none / 0) (#230)
by Lord of the Wasteland on Mon Nov 18, 2002 at 07:58:18 PM EST

Even ACE doesn't eliminate the problem of race conditions. At least in 5.2.1, the Logging Strategry test has a race condition where the Reactor can be freed before the subsidiary thread is done using it.

[ Parent ]
Of course it doesn't (2.00 / 1) (#238)
by avdi on Mon Nov 18, 2002 at 09:19:01 PM EST

Using STL doesn't eliminate buffer overflows either.  Your point?

--
Now leave us, and take your fish with you. - Faramir
[ Parent ]
My point (4.00 / 1) (#240)
by Lord of the Wasteland on Mon Nov 18, 2002 at 09:26:43 PM EST

It's just funny that there's an error in the examples. It's also tickled my sense of synchronicity that I read the article while taking a break from debugging ACE code.

[ Parent ]
A few comments on your comment.. (none / 0) (#411)
by sudog on Fri Nov 22, 2002 at 01:42:15 AM EST

"many UNIX programmers have gotten it into their skulls that fork-and-exec is an elegant and powerful idiom, suitable for anything threads are good for."

Highly doubtful. No UNIX programming books I've ever read suggest that fork and exec are "powerful" or "good for what threads are good for." And that includes books from Stephens, books from Aho, and CSC texts, BTW.

In fact, they all seem to warn of the inherent SLOWness of fork-exec and all the overhead of process duplication. The only book that doesn't is "Application Programming for Linux" which says the same but makes Linux an exception because it its "lightweight" stuff.

"The idea that you would want to duplicate an entire process, with all the painful IPC overhead that entails and the ugly fact of having a single piece of code do two completely different tasks based on the return of the fork() call is absurd on it's face.  Many coders still think otherwise however."

I get a kick out of this note. "IPC" overhead? It's possible I'm misinterpreting your meaning. It's also possible I'm misinterpreting your IPC abbreviation. But since when does the act of duplicating a process automatically "entail" inter-process communication?

"In conclusion:  threads are not scary.  Here be no dragons.  I recommend to anyone who thinks thay are frightening or dangerous that they shake off their superstition, read some good, modern books on the subject, and dive in."

Threads aren't frightening. They're easy. Simple. Seductive. A programmer sees a thread and sees a simple way to design his software without figuring out what must happen before what and when. Event-driven? That's harder. Heh--why do you think so many people use threads? :-)


[ Parent ]

rpm in RedHat 8.0 (2.00 / 1) (#179)
by starheart on Mon Nov 18, 2002 at 03:52:54 PM EST

This article reminds me of the problem the rpm developer at RedHat is currently having with rpm. Since beta till now he has been fighting concurrency and locking issues in rpm. I first reported the bug during beta. He thought he had fixed it. RedHat releases 8.0 and the problem is back in the forms of 2-3 bugs at least. Many people are delaying deployment of 8.0, because of this problem with rpm. The really sad part is how long it is taking him to fix the bugs. 8.0 has been out for a few months and there are still hang bugs in the lastest version of rpm.

Misleading (3.14 / 7) (#189)
by marx on Mon Nov 18, 2002 at 04:33:14 PM EST

the locks are still being locked and released like a terrorist in the Palestinian authority
LOL! How funny that you managed to sneak in a kick to the nuts of the Palestinians in a technical article.

This totally contradicts my view of Israeli intellectuals as vengeful oppressive assholes, thank you.

Now to move past the distractions:

The point you make is that threads, if used incorrectly, can lead to bugs which are hard to debug. This is true. However, the same is true of almost any feature of popular imperative languages. What you are attacking is not threads, but mutable variables and far-reaching variable declarations.

The problem in the example you construct does not really have to do with threads. What happens is that a variable is declared somewhere, and then is modified and used in wildly separate places. This is what is causing the mayhem in your example, not the fact that you have to use locks when accessing shared resources when using threads.

If your example would have been single-threaded, and the programming mistake would have consisted of the same buffer misallocation, the debugging problem would be exactly the same. You would have crashes occuring in places seemingly completely unrelated to where you made the actual error.

You say in a comment:

Seperate address space is good: it means different processes can't scribble on each other's variables.
So you advocate mutable variables, it's implicit in your statement that the alternative is not even considered. Why are you doing this, since your number one priority is keeping novice programmers from shooting themselves in the foot?

The reason threads have become popular is that they are a simple concept, are easy to use, and are powerful, i.e. the same reasons why C/C++ have become so popular, even though they are little more than assembly with some pretty syntactic sugar on top, almost literally a death trap for novice programmers.

So when you're telling us that we should jump through the hoops of forking off processes and explicitly sharing each and every variable we want to use, you could just as well be telling us that we should use a functional language instead of C, to protect us from our own stupidity. All I can do is wish you luck in this endeavor.

Join me in the War on Torture: help eradicate torture from the world by holding torturers accountable.

This post is Offtopic (none / 0) (#398)
by Peaker on Thu Nov 21, 2002 at 03:06:13 PM EST

This totally contradicts my view of Israeli intellectuals as vengeful oppressive assholes, thank you.

I'd like to know why you hold this view, and how you suggest Israel reacts to attacks on its citizens :)

[ Parent ]

My suggestions (none / 0) (#422)
by marx on Sun Nov 24, 2002 at 04:35:50 AM EST

I'd like to know why you hold this view
Because of what I've seen and heard of and from Israeli intellectuals on TV, Israeli newspapers etc. Granted, most of them have probably been politicians of some kind, but that doesn't excuse anything.
how you suggest Israel reacts to attacks on its citizens :)
What every other reasonable country does (hint: this does not involve firing missiles into residential areas or collective punishment of an entire minority).

First of all, don't be an asshole to countries and people around you. Be nice. No matter what you do, you're going to end up interacting with other people around you. If you're nice to them, they will be nice to you, otherwise not. The only outcome of being an asshole to everyone is that you will have to indefinitely build stronger and stronger defenses, which will invariably prove to be insufficient.

If a murder is committed, then it's a police matter. If a murder is committed by a foreign national, then it's a matter for the police in that country. If they systematically ignore your requests and are uncooperative, then you impose stricter visa regulations, or a similar mechanism. You don't murder them, invade their country, and do their job for them by force.

Join me in the War on Torture: help eradicate torture from the world by holding torturers accountable.
[ Parent ]

Naive (none / 0) (#426)
by Peaker on Thu Nov 28, 2002 at 03:17:24 PM EST

Because of what I've seen and heard of and from Israeli intellectuals on TV, Israeli newspapers etc. Granted, most of them have probably been politicians of some kind, but that doesn't excuse anything.

Interesting, I agree with many of the Israeli intellectuals (and disagree with some).

What every other reasonable country does (hint: this does not involve firing missiles into residential areas or collective punishment of an entire minority).

How many countries have this problem? Israel only fires missles at those who fire at it. Those who fire those missles hide amongst civilians, so the only reasonable response, besides "stricter visa regulations", which probably wouldn't stop someone from shooting a missle at you.

First of all, don't be an asshole to countries and people around you. Be nice. No matter what you do, you're going to end up interacting with other people around you. If you're nice to them, they will be nice to you, otherwise not. The only outcome of being an asshole to everyone is that you will have to indefinitely build stronger and stronger defenses, which will invariably prove to be insufficient.

Look at the period of Barak - when he offered the Palestinians quite a bit of goods... And they refused. This theory doesn't always seem to work.

If a murder is committed, then it's a police matter. If a murder is committed by a foreign national, then it's a matter for the police in that country. If they systematically ignore your requests and are uncooperative, then you impose stricter visa regulations, or a similar mechanism.

Hehehe. This was indeed a good laugh for me. As if weekly bombings of Israeli citizens, killing dozens every week, should be responded by being nice and imposing "stricter visa regulations", for a few years - until which time they will hopefully stop bombing your restaurants, clubs and schools.

Ofcourse, during those years, Israelis cannot really go out of their house due to fear of being murdered - since no counter action is taken by Israel, but ofcourse this is no problem, because if you are nice to them, they'll be nice to you, riiight.

You don't murder them, invade their country, and do their job for them by force.

If I have to choose this way to prevent the weekly murder of dozens of innocent citizens, or to choose to be "nice" hoping for a change from the extremist Islamic groups, I'd choose invasion, invasion, invasion.

[ Parent ]

War and peace (none / 0) (#429)
by marx on Thu Nov 28, 2002 at 10:43:04 PM EST

I'd choose invasion, invasion, invasion.
Some people want peace and some people want war. If you want war, then why are you complaining about bombings?

Join me in the War on Torture: help eradicate torture from the world by holding torturers accountable.
[ Parent ]

Superficial (none / 0) (#430)
by Peaker on Fri Nov 29, 2002 at 09:15:26 AM EST

That was one of the most superficial analysis I've ever heard.

I want peace - but to get peace you don't just stand there and take whatever is thrown at you.

To get peace: You sometimes have to fight back.

[ Parent ]

Warhawks (none / 0) (#431)
by marx on Fri Nov 29, 2002 at 10:38:07 AM EST

To get peace: You sometimes have to fight back.
That's not what you said though. What you wanted to do was to "invade, invade, invade" the land of people who have done you nothing. That's not "fighting back", that's starting a war. And how do you fight a war against suicide bombers anyway? Kill them?

It's clear from your writing that you don't see war as the last option, but as the first.

I think Michael Franti put it well: "You can bomb the world to pieces, but you can't bomb it into peace".

Join me in the War on Torture: help eradicate torture from the world by holding torturers accountable.
[ Parent ]

How is it clear? (none / 0) (#432)
by Peaker on Fri Nov 29, 2002 at 12:43:41 PM EST

I want to invade, invade, invade the territory that spawns many terrorist attackers and harnesses terrorist planners, organizers, weapon manufactuers, etc. To say that those terrorists have done me nothing is bollocks, and to say that those people are not all terrorists is true - but that's where the terrorists are hiding.

Just note that since Sharon invaded the territories, the terror success levels in Israel dropped by far - and many terrorist headquarters in those territories were destroyed.

The first option was not invading - asking the PA to prevent it.

When that didn't work, Sharon tried the second (and last) option, is to invade the territories and destroy the terrorist infrastructure. The fight back against terror had worked - if only for the short term.

Why do you think or assume that my call for invasion is the first option, and not the last, which it is?

[ Parent ]

No it's not (none / 0) (#433)
by marx on Sat Nov 30, 2002 at 12:13:54 AM EST

It's not the only option left. Since you say the terrorists are hiding there, why not simply not allow anyone to enter Israel from there? Israel pulls out all forces and refuses anyone to enter until there are no more bombings. There are many more options, but all you want to do is invade.

This is the core issue. Israel does not primarily want peace, it wants territory. A simple indicator of this is the settlers. Having settlers does absolutely nothing to help the peace issue, in fact it does a lot to sabotage it, but it helps a lot with the territory issue.

If you don't agree with this, it's enough to state just one thing which the settlers do to help the peace issue.

Join me in the War on Torture: help eradicate torture from the world by holding torturers accountable.
[ Parent ]

Reply (none / 0) (#434)
by Peaker on Sat Nov 30, 2002 at 08:00:36 AM EST

It's not the only option left. Since you say the terrorists are hiding there, why not simply not allow anyone to enter Israel from there? Israel pulls out all forces and refuses anyone to enter until there are no more bombings. There are many more options, but all you want to do is invade.

You are merely proving your ignorance on the matter here. Israel tries to refuse anyone to let in - but its impossible to do with an army like Israel's, and such a long border. Israel is now building a huge wall on most of the border, which is a huge project, but until its done, there is no way to even dream of blocking enterance.

Even when the wall is up, there are cities like Jerusalem in which there is no way to separate the mangled communities. And the wall is problematic because it sets up a border before there was any agreement on one.

If you looked this situation up and learned a bit more about it - you'd know that your suggestion here is utterly impractical.

This is the core issue. Israel does not primarily want peace, it wants territory. A simple indicator of this is the settlers. Having settlers does absolutely nothing to help the peace issue, in fact it does a lot to sabotage it, but it helps a lot with the territory issue.

Israel does not want territory, it wants peace. A simple indicator of this is the agreements Israel has signed with Egypt, Jordan, and the Palestinians. The settlers don't help with anything - they are extreme right-wing people with stupid beliefs who force themselves into those territories, mostly illegally. Its simply too hard to act against those, politically, because there's a huge lobby against pulling them back. I am one of the big supporters of pulling the settlers the hell back.

Also note that those settlers were about to be pulled back as part of the agreements Barak tried to sign, but then the Intifada broke, and Israel didn't want to pull the settlements back because it would be taken as a reward to the terrorist activities.

If you don't agree with this, it's enough to state just one thing which the settlers do to help the peace issue.

Again, I agree that the settlers should be pulled back. But its not the core of the issue. The core is that there are two peoples living in nearby territories with a border too large to block or protect, such that they can easily enter and bomb restaurants and so - and the only solutions are to block enterance (incredibly difficult to impossible) or to destroy their HQ. Both of these options are being worked on now. If only one was, then the bombings would continue until the other was complete, and even then.

Until there's a way to block the enterance, and there might never be, I call to invade and destroy the terrorist headquarters. You are calling to do unpractical things with no research or understanding of the matter.

[ Parent ]

Subject (none / 0) (#435)
by marx on Sat Nov 30, 2002 at 09:06:36 AM EST

You are merely proving your ignorance on the matter here. Israel tries to refuse anyone to let in - but its impossible to do with an army like Israel's, and such a long border.
So let me get this straight: it's easier to monitor an entire territory than to monitor a border to that territory? Basically anyone can make or acquire these kinds of bombs, so it truly is the entire territory which must be monitored. If people want to continue with the bombings, then I guess it will always be impossible to stop them completely, just as it's impossible to stop any crime. However, if you choose your method of prevention, then you will just fuel the desire to do more bombings.
Israel does not want territory, it wants peace. [...] Its simply too hard to act against those, politically, because there's a huge lobby against pulling them back. I am one of the big supporters of pulling the settlers the hell back.
There cannot simultaneously be a desire to pull them back and a desire to not pull them back. You said it yourself, it would be political suicide to stop the settlers, thus Israel wants the settlers there. And since the whole point of the settlers is to expand territory, Israel chooses to expand its territory over peace.
The core is that there are two peoples living in nearby territories with a border too large to block or protect, such that they can easily enter and bomb restaurants and so
Almost every country on the face of the earth has two peoples living in nearby territories with large borders. Yet we don't see these kinds of problems. I will be the first to say that most Arab states have ass-backward governments (including Palestine), but Israel has a large part of the blame in this conflict, and it shouldn't, because it's a supposedly enlightened western democratic state.

Also, I think you're focusing too much on the suicide bombings. You claim that Israel is occupying Palestine to be able to prevent suicide bombings. However, the bombings are a relatively recent phenomenon. Israel has occupied Palestine for over 30 years. What is happening is that the occupation is static, and the reason changes. Always some new excuse as to why Israel must occupy and invade.

I have yet to hear a good argument why Israel should not be defined as a fascist state. It keeps a large ethnic minority in a militarized territory, constantly terrorizing the people, stripping them of human dignity and rights. Why should I tolerate fascism? Look at South Africa and see what eventually happens. Does Israel really want to go down in history as a new South Africa?

Join me in the War on Torture: help eradicate torture from the world by holding torturers accountable.
[ Parent ]

Reply (none / 0) (#436)
by Peaker on Sat Nov 30, 2002 at 01:03:29 PM EST

So let me get this straight: it's easier to monitor an entire territory than to monitor a border to that territory? Basically anyone can make or acquire these kinds of bombs, so it truly is the entire territory which must be monitored. If people want to continue with the bombings, then I guess it will always be impossible to stop them completely, just as it's impossible to stop any crime.

No, monitoring an entire border is more difficult than monitoring the entire territory, because when doing the latter, you can put a curfew and limit the free movement of everyone, while you're destroying the terrorist infrastructure. Also note that monitoring is not the goal of invasion, but rather destroying the explosives' labs, etc. Note that monitoring the border failed, and the recent invasions were relatively successful at disarming the terrorist groups.

However, if you choose your method of prevention, then you will just fuel the desire to do more bombings.

The desire was always there, and is there to stay. That's what organizations such as Hamas exist for. If they dropped this desire, they'd lose all of their power, thus they must fuel this desire and they do so by advertising anti-Israel propoganda and such, even in the most peaceful of times (after Rabin/Peres, etc.).

There cannot simultaneously be a desire to pull them back and a desire to not pull them back.

A political system, a state, is a complicated entity, and contradicting desires can indeed simultaneously exist.

You said it yourself, it would be political suicide to stop the settlers, thus Israel wants the settlers there.

It wouldn't be political suicide normally, but only in a time of terror, when no rewards of any kind can be given, not even moral ones such as leaving the territories. This, at least according to most oppinions.

And since the whole point of the settlers is to expand territory, Israel chooses to expand its territory over peace.

You're neglecting mention of all the signed Israeli agreements where it has already agreed to taking back the settlements, and give territories, with the end of the entire dispute. Unfortunatly, Arafat wasn't willing to sign those agreements.

Almost every country on the face of the earth has two peoples living in nearby territories with large borders. Yet we don't see these kinds of problems.

Are those nations at war? Israel doesn't have these kinds of problems with Syria, Jordan or Egypt.

I will be the first to say that most Arab states have ass-backward governments (including Palestine), but Israel has a large part of the blame in this conflict, and it shouldn't, because it's a supposedly enlightened western democratic state.

Israel has a big historic part in this conflict, I agree, but I don't believe that there are any better alternate ways of action today, besides invasion, while building a defensive border.

Also, I think you're focusing too much on the suicide bombings. You claim that Israel is occupying Palestine to be able to prevent suicide bombings. However, the bombings are a relatively recent phenomenon. Israel has occupied Palestine for over 30 years. What is happening is that the occupation is static, and the reason changes. Always some new excuse as to why Israel must occupy and invade.

This is the best point you make, and I even agree with most of it. Yes, Israel has occupied territories that it shouldn't have in the past, and for bad reasons. However, I do believe that the always changing reason for occupation is a real reason - only that I do not think it was justifiable then. I do think it is justifiable now. The reason for occupation between 1967 and 1987 (When the first intifada broke), was the terrorist attacks from Arafat's Fatah movement, and the occupation did seem to "end this" (on the Israeli side), however immoral it was. However, it was probably possible to sign some sort of cease-fire agreement in 1967 already, which would be a better alternative for all involved (especially since the terror was not as unbearable as it is today), which is why I find the occupation back then unjustified, while now justified (at least temporarily).

I have yet to hear a good argument why Israel should not be defined as a fascist state.

Huh?

It keeps a large ethnic minority in a militarized territory, constantly terrorizing the people, stripping them of human dignity and rights.

While this is partial truth and does happen in some places in some times (though not as a policy), how does it have anything to do with facism?

Why should I tolerate fascism? Look at South Africa and see what eventually happens. Does Israel really want to go down in history as a new South Africa?

South Africa was an oligarchy, while Israel is actually two states, in which Jews are the ruling majority, with parliamental representation of the entire population, and another 'country' in which Jews(/Israel) is not ruling at all, only temporarily occupying for security-reasons.

The occupation of 1967-1987 was about to end with Rabin and Peres's peace attempts, but those peace attempts were disrupted by terror movements that the PA did not try to contain, and by Arafat's complete lack of compromise on a request that was agreed by everyone to mean the destruction of Israel.

[ Parent ]

Fascism (none / 0) (#437)
by marx on Sun Dec 01, 2002 at 05:43:11 AM EST

how does it have anything to do with facism?
A fascist state is normally defined as a state where upholding the nation or race is held above all and where you have ethnic segregation and brutal military enforcement.

Regardless of definitions, the actions of Israel give me associations to South Africa and also to a certain extent Nazi Germany. You have an almost total disregard for the human worth of people of a certain ethnicity. As you say, when you occupy, you can curfew and let the military roam in the territory. This type of law enforcement would never be accepted in Jewish areas, only when Palestinians are the victims. Israel would never fire a rocket into a Jewish residential area to assassinate a criminal living there.

You say that it's two countries, but de facto it's not. The ruler of the Palestinian territories is ultimately Israel. If you make the thought experiment of thinking of the two areas as both being included in the state of Israel, then I think you would agree with me that such a state should not exist in the modern world, especially not under the guise of democracy and humanism.

Join me in the War on Torture: help eradicate torture from the world by holding torturers accountable.
[ Parent ]

Reply (none / 0) (#438)
by Peaker on Sun Dec 01, 2002 at 03:58:39 PM EST

A fascist state is normally defined as a state where upholding the nation or race is held above all and where you have ethnic segregation and brutal military enforcement.

No, facism is about the importance of the state is above all, and that the interests of the state are the most important. If it can benefit itself by destroying half of its population, so be it, and if it has to murder its neighbours for its benefit, it will.

You can hardly claim Israel is using its force to destroy its "problematic" population, nor is it using it to harm its neighbours (because not much of its neighbours would be left).

Regardless of definitions, the actions of Israel give me associations to South Africa and also to a certain extent Nazi Germany. You have an almost total disregard for the human worth of people of a certain ethnicity. As you say, when you occupy, you can curfew and let the military roam in the territory. This type of law enforcement would never be accepted in Jewish areas, only when Palestinians are the victims.

It would be accepted if those Jewish areas spawned terrorists that are otherwise unstoppable.

Israel would never fire a rocket into a Jewish residential area to assassinate a criminal living there.

Because Israel has the alternative power to arrest that person. Note that Israel does not shoot rockets into Palestinian residential areas that are currently occupied, but in that case it arrests those people.

Also note, that despite what foreign media tries to display, the assinations are not against criminals, but against activists who are in the process of planning a terrorist action.

You say that it's two countries, but de facto it's not. The ruler of the Palestinian territories is ultimately Israel.

Israel does not rule the civilian side of the Palestinians, only the security side, for pure security reasons.

If you make the thought experiment of thinking of the two areas as both being included in the state of Israel, then I think you would agree with me that such a state should not exist in the modern world, especially not under the guise of democracy and humanism.

Because no country has a huge minority supporting murderous actions against the majority, and has huge underground activities supporting these actions.

[ Parent ]

-1 (2.33 / 6) (#191)
by X3nocide on Mon Nov 18, 2002 at 04:49:30 PM EST

Edit and resubmit as "Humor."

pwnguin.net
Sequential x Parallel machines, language structure (5.00 / 6) (#195)
by cribeiro on Mon Nov 18, 2002 at 05:04:39 PM EST

A considerable part of the problem is the fact that we are still (mostly) limited to the textual representation of software, that is exceptionally well suited to sequential machines, but not so for stuff such as threads. I believe that a different representation is needed for non-sequential machines in general. A bit of history will be helpful here.

Primitive computers were mechanical or electro-mechanical machines. Programming was limited to some form of hardware customization, by means such as changing gears and connecting patch cables. The concept of 'programming language' didn't exist; drawings and diagrams were used instead, together with mathematical formulas that described the effect of each particular construct. In many cases, the processing wasn't sequential, for mechanical machines are parallel by nature. All those reasons combined conspired to make textual representations not very effective.

After the introduction of stored programs (an idea pushed forward by von Neuman), it became possible to think about 'programs' as textual constructs. Programming languages were invented; first assembly languages, then the first so-called 'high level' languages, and so on. A lot of research was done, and some neat theories were developed. We now have OOP, and functional programming, and dozens of alternative languages for almost all tastes. However, most practical alternatives to traditional programming languages are still text-based, for several reasons:

  • it's easy to edit, print, and to process using a computer;
  • it's easy for a human to read;
  • it's even easier to spell.
The list above is far from complete, but it is intriguing nonetheless. Most of the reasons that I can think of to stick with textual programming languages are related to human limitations in one form or other:
  • limitations of the input/output devices;
  • limitations of human communication methods;
  • limitations of the teaching process, specially in western societies where non-textual abstractions - for example, geometric or mathematic thinking - are notably absent from school.
Back to the original question - are threads harmful? - I believe the answer is not. The problem (in my not-so-humble opinion) is that we're trying to map non-linear, multidimensional problems to textual representations that are linear in nature. More advanced languages represent only a partial cure for this problem, in the same sense that a good projection helps to visualize 3D shapes on 2D space. A more symbolic approach is needed, but I don't believe it will happen anytime soon.

Question (4.00 / 1) (#204)
by dazk on Mon Nov 18, 2002 at 05:54:26 PM EST

How would one write some kind of server application that get's locked when listening for input on a socket? Is it really that bad to have a thread for that, that dumps data in a buffer after it receives it. The main program can check wether there is something in the buffer and use some method to retrieve the information that makes sure, the buffer is locked. Of course the thread locks the buffer before writing to it.

That way your main program can access the buffer whenever it wants and the thread can run without problems.

I you use an object oriented language you can easily put the buffering in a class that spanws it's reader thread. You can even implement an observer pattern by registering your main application with the networking class and that class informs the main program about changes.

I wonder what would be so bad about something like that.
----- Copy kills music! Naaah! Greedyness kills Brain! Counter: Bought 17CDs this year because I found tracks of an album on fileshare and wanted it all.

Just for example (none / 0) (#234)
by Kal on Mon Nov 18, 2002 at 08:50:50 PM EST

Conviently enough I was working on just this earlier today.  I'm using Tcl/Tk, so I've got the advantage of an event based language, and here's what I did.

I start up a listen socket with "socket -server AcceptConnection 45000".  This tells the server to open a socket on port 45000 and wait for a connection.  

Once it gets a connection it calls the AcceptConnection method:
method AcceptConnection {sock addr port} {
  set ClientProxy("$addr:$port") [ClientProxy \#auto $sock]
}

The ClientProxy object takes that socket from the AcceptConnection method and takes over all communication between the client and the server for that connection using fileevent calls:

::itcl::class ClientProxy {
  constructor {sock} {
    set itsSocket $sock
    fileevent $itsSocket readable [list $this ReadSocket]
  }

  method ReadSocket {} {
    set input [gets $itsSocket]
    ->Parse input and decide what to do with it here.
  }

}

This way of doing it is quick to program and easily integrated with other languages.

[ Parent ]

Where is the buffer? (none / 0) (#314)
by greenrd on Tue Nov 19, 2002 at 08:50:39 AM EST

I don't think you understood the post to which you replied. Where is the buffer? If there's implicit buffering, what if you want to use your own buffer?


"Capitalism is the absurd belief that the worst of men, for the worst of reasons, will somehow work for the benefit of us all." -- John Maynard Keynes
[ Parent ]

*grumble* (none / 0) (#341)
by Kal on Tue Nov 19, 2002 at 01:08:24 PM EST

I had a real good reply made up to this, but netscape bombed once again and I lost it.

I short, the "socket -server" option answers the "How would one write some kind of server application that get's locked when listening for input on a socket?" question.

As for buffering on the client side I'd do something like this:

set socket "make connection to the server"
fileevent $socket readable "run this method"
"Go do other things now"
...

"Run this method" {
  "do something with this data"
}

[ Parent ]

Exactly his example... (none / 0) (#250)
by pla on Mon Nov 18, 2002 at 10:58:38 PM EST

The standard "producer/consumer" thread pair that you describe sounds very like the parent article's example of a buffer getting out of sync.

Basically, you can do it with locks. But possibly quite a lot of them, with a level of nesting that lends itself to easy mistakes while implementing it, nevermind going back a year later to add a feature or repair a (possibly unrelated) bug.

And if you can honestly say that you will never, ever have to go back and make changes to a section of code a year later, I can also confidently say you do not work as a software engineer for a living. ;-)


[ Parent ]
IO multiplexing (4.00 / 1) (#290)
by Znork on Tue Nov 19, 2002 at 04:29:35 AM EST

Try select() combined with non-blocking read(). That's how you can program non-threaded server applications without using threads.

You have a main loop that runs around its various timers and the select call checks for readable or writable fd's. As soon as one is available, you read from it into the fd's buffer (depending on your application you might want to limit the read to a certain size to allow for multiplexing large amounts of fast incoming data). If the read would block due to starvation it will end and you get back to your main loop.

You accomplish the same thing and without threads.

Not that threads are inherently bad, they're just inherently very very risky and hard to debug.

[ Parent ]

Yeah and (none / 0) (#315)
by greenrd on Tue Nov 19, 2002 at 08:55:15 AM EST

What happens when you are busy doing some operation which uses the CPU a lot?

You have to manually yield. This is bad separation of concerns. Why should your math programmer have to worry about what the network IO code is doing??

If you have a separate buffering thread and the scheduler is able to make it active quickly and often enough, you can carrying on receiving "arbitrary" amounts of data while computing something else.


"Capitalism is the absurd belief that the worst of men, for the worst of reasons, will somehow work for the benefit of us all." -- John Maynard Keynes
[ Parent ]

What happens... (none / 0) (#317)
by Znork on Tue Nov 19, 2002 at 09:20:07 AM EST

Is you use a signal handler and setitimer (). If you have to. Again, you dont have to use threading to accomplish this (altho, once you start off with signal handlers you're going to start getting some of the same problems there unless youre careful).

Of course, at that point it starts becoming a bit contrived and it depends on what you actually want your application to do.

If you're doing large amounts of processing depending on the input from a single network connection I'd say fork.

If you're doing large amounts of processing that cannot be easily chopped up into pieces on common data from all the separate connections then you've probably hit a problem where threading would be a good idea, especially if you need SMP scaling. With common data being processed based on incoming network traffic you're gonna have some serious mutex pain tho. That math programmer had better worry about what the network code is going to do to his data at arbitrary times.

[ Parent ]

Tom Christiansen, Bach, and parallel languages (5.00 / 3) (#209)
by cribeiro on Mon Nov 18, 2002 at 06:28:08 PM EST

I've followed the link to Tom Christiansen's post - it's one of those posts that used to make Usenet great. However, I have a different interpretation of Tom's words.

Threads are not inherently bad. Just hard, and they can be more or less hard depending on the tools available. Following Tom's example, the works of Bach are exceptional examples of parallelism in music. The fact that Bach was able to write fugues for a single-threaded instrument (the violin) shows how good he was. However, even Tom contends that it is much easier to write 'parallel' music for instruments which are multi-threaded - the organ, for example.

if you apply the same thinking to programming, you'll see that the problem is not with threading, but with the tools used to work with them. True masters can take inherently single-threaded languages, such as C, and write wonderful works of art. Most people don't even know where to start. Better tools surely make things easier - not absolutely easy, in fact, but at least manageable.

In short, threads are not at fault here. The problem is with the languages, tools and methodologies that we are using. These tools served us tremendously well to work out a wide range of sequential problems, but simply aren't well suited to a bigger class of problems.

Good except.. (none / 0) (#410)
by sudog on Fri Nov 22, 2002 at 01:23:48 AM EST

,,for the poke at languages. :-) C is fine, just takes longer to do something with it. Methodologies, maybe tools, but don't diss the language. :-)

[ Parent ]
I disagree... languages are also to blame (none / 0) (#428)
by cribeiro on Thu Nov 28, 2002 at 07:50:53 PM EST

I respectfully disagree with your position. C is fine for a lot of problems; if you push it hard enough, you can solve any programming problem with C. In a sense, the same can be said about assembler, Pascal or Perl - if you're proficient enough, you can find a solution.

Back to the original discussion, the problem is not with the C language in itself, but with the nature of languages. A language is a formal way to express an idea. Although many languages can potentially represent any idea, some languages are better suited than others to represent some classes of problems. For instance, some sciences, such as mathemathics, chemistry or biology have developed their own symbolic languages to express their particular problems. Try to use C for that.

One could probably say that this is not a good example, because we are mixing different stuff here - programming and chemistry, for example. But that's exactly my point - parallel problems can't be clearly expressed in any language designed for sequential problems, in the same way you can't precisely describe a chemical structure with plain english.

In short, if you think strictly in terms of programming, as taught today, you're right - languages are not to blame. But if you open your mind for a bigger range of problems, you'll see how important the choice of language is. It goes beyond the relative cleanliness of the solution - it changes the way we see and understand the problem.

[ Parent ]

Sorry, not buying it.. (none / 0) (#442)
by sudog on Wed Dec 11, 2002 at 02:38:38 AM EST

It's the test of your mettle to be able to (as fluently) program in what you consider to be an inferior language when a better one exists to facilitate your design.

Also, C isn't just "fine" for a lot of problems. Why do you think operating systems are written primarily in C even after all these years?

And my mind isn't closed--that's why I'm currently working in OCaml, which is as different from C as it gets.


[ Parent ]

Threads cosidered harmful (1.33 / 3) (#211)
by Zero Sum on Mon Nov 18, 2002 at 06:41:07 PM EST

It isn't threads per se that are the problem, it is the access to resources. On an operating system using capabilities, not ACLs, there should be no problem.

So it is the OS, not the threads.
Zero Sum - Vescere bracis meis

Unfortunately (4.00 / 1) (#218)
by kraant on Mon Nov 18, 2002 at 07:12:08 PM EST

Unfortunately for threads a large proportion of the shared resources that are causing this problem is the shared memory space. This makes it somewhat hard to determine which thread has rights to which part of the process memory.
--
"kraant, open source guru" -- tumeric
Never In Our Names...
[ Parent ]
Eh? (4.00 / 1) (#247)
by greenrd on Mon Nov 18, 2002 at 10:30:56 PM EST

Please explain how capabilities are at all relevant to, say, solving deadlocks. Or any other threading bug.


"Capitalism is the absurd belief that the worst of men, for the worst of reasons, will somehow work for the benefit of us all." -- John Maynard Keynes
[ Parent ]

What do you think of... (3.00 / 2) (#220)
by Kitty Mai on Mon Nov 18, 2002 at 07:18:26 PM EST

...Python's threading API?

Also, are there any good Python frameworks for writing networked applications?
Check out my costume! ^_^

Python and threading (4.50 / 2) (#225)
by kuran42 on Mon Nov 18, 2002 at 07:23:45 PM EST

Python is a language that is especially poorly suited for writing threaded apps. The interpreter has something called the Global Interpreter Lock (GIL). While this keeps you from hurting yourself with threads somewhat effectively (all the built-in types are thread-safe because of it), it sends performance down the toilet. Even if two threads are accessing totally different objects, they still have to acquire the GIL before they can do it. The end result is that your program runs like crap.

--
kuran42? genius? Nary a difference betwixt the two. -- Defect
[ Parent ]
More on Python and threading (5.00 / 2) (#242)
by sigwinch on Mon Nov 18, 2002 at 09:31:25 PM EST

Python is a language that is especially poorly suited for writing threaded apps. The interpreter has something called the Global Interpreter Lock (GIL).
Which is released by most I/O primitives. Threads that spend most of their time waiting for I/O or events don't suffer much.

It's quite easy for extensions written in C to temporarily release the GIL. If I remember right, the Numeric library does this for matrix calculations, which lets a Python program doing signal processing keep multiple processors busy.

So whether Python sucks for a multithreading depends on what you're doing. For many realistic apps, it isn't so bad.

--
I don't want the world, I just want your half.
[ Parent ]

Python's Threading API (4.50 / 2) (#226)
by moshez on Mon Nov 18, 2002 at 07:24:47 PM EST

Well, for what it tries to do (that it, make sure only one thread ever runs Python code, but let C extensions (such as blocking code) free lock and reaquire it) it is good. For what it doesn't try to be, it is a relatively poor match, as always

Re: Python Networking API: I'm part of the Twisted development team, so naturally I'm biased. Nonetheless, Twisted is a very good choice! It is completely event-based, but does have native support for threads, and often frees you from many of the threading worries by letting you pretend threads are just another asynchronous support for events. It has support for database programming, on top of Python's DB-API. It has a multitude of protocol implementations available, integrates with a gazillion windowing toolkits (GTK+, Tk, Win32) and has its on asynch translucent remote object protocol (Perspective Broker).



[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
kludged in like many of Python's features (2.66 / 3) (#243)
by avdi on Mon Nov 18, 2002 at 09:34:52 PM EST

Threading really seems to be one of those things that is considered from the very start of a language's evolution to work.  Either, as in C and C++, a conscious decision is made to not provide a standard API, but to craft the language so that it is amenable to any number of third-party threading implementations; or make threading a basic part of the language (like Java, and I believe Eiffel).  Perl and Python came  late to their threading implementations; and Ruby's userland threads, while better integrated, have evere defficiencies.  It's no wonder that people who come to programming via scripting languages are put off by threads.

--
Now leave us, and take your fish with you. - Faramir
[ Parent ]
Costume and threading. (none / 0) (#395)
by Anonymous Hiro on Thu Nov 21, 2002 at 12:40:35 PM EST

Costume and content look good ^_^

But I've seen a different Kitty Mai somewhere else. So the costume is yours but which Kitty Mai are you? :-)

Hmm, anime costumes do involve threading, so am I still on topic?


[ Parent ]

sigh; political comment (1.00 / 4) (#224)
by daniels on Mon Nov 18, 2002 at 07:23:32 PM EST

So, here I am thinking: hey, I don't agree with this, but it's a reasonable article. Then the following sentence leaps out:
Unfortunately, the locks are still being locked and released like a terrorist in the Palestinian authority.

Why the hell did you have to screw up an otherwise decent article with a pithy political snipe?

Leopard and spots, I guess.
--
somewhere in space, this may all be happening right now
moshez is from Israel (nt) (4.00 / 2) (#227)
by BinaryTree on Mon Nov 18, 2002 at 07:25:02 PM EST



[ Parent ]
Yes, but ... (1.00 / 2) (#372)
by daniels on Wed Nov 20, 2002 at 10:20:15 AM EST

I know, but this doesn't excuse it in ANY way.
--
somewhere in space, this may all be happening right now
[ Parent ]
Because (4.28 / 7) (#272)
by moshez on Tue Nov 19, 2002 at 01:58:06 AM EST

...people who don't have humour, especially about world politics, should be mass-genocided, and their bodies used to fertilize the soil.

Who's with me? We can start a new hate group called the "Humor Heads", get together and throw burning molotov cocktails at people in ties who never smile.

Humor Power!

See me personally about the secret hand shake. Any connection to the illuminati is purely coincidental.



[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Ah, brilliant (1.00 / 4) (#339)
by DominantParadigm on Tue Nov 19, 2002 at 12:21:30 PM EST

...people who don't have humour, especially about world politics, should be mass-genocided, and their bodies used to fertilize the soil.

Who's with me? We can start a new hate group called the "Humor Heads", get together and throw burning molotov cocktails at people in ties who never smile.

The typical response of the Jew when challenged, bullshit their way out of the situation without answering the question.

HOHOHO that was a funny joke, no?



Caller:So you're advocating bombing innocent children? Howard Stern:Yes, of course!


[ Parent ]
You forgot state machines (4.60 / 5) (#245)
by MichaelCrawford on Mon Nov 18, 2002 at 09:46:32 PM EST

You neglected to mention state machines. I think state machines are how threads are actually implemented in a kernel, but you can do them from user programs too.

I worked on a TCP/UDP test tool for the Mac back in 1989. Macs didn't have threads back then and only allowed cooperative multitasking. It needed to handle asynchronous communication (non-blocking I/O) with multiple TCP streams and UDP pseudo-streams between a bunch of different macs. It handled this with a big loop that went through a large switch statement. Each time through the loop, a task got to do one thing, and then went to the next state.

The original tool (which I inherited) didn't work too well, so I convinced the management to let me write a new one. It was my first real C++ program and one of the first test tools done in C++ at Apple.

My new tool was also a state machine, and with it I could have a single mac do asynchronous communication with UDP and TCP on 64 streams simultaneously (the limit of MacTCP's resources). It was pretty impressive to network up a few dozen macs in a lab and have them all running my tool and then watch with a protocol analyzer.

This kind of thing wouldn't have been possible at the time without a state machine.

Also the Mac version of my Raindrops screensaver is a state machine. The BeOS version uses threads, but I found that I couldn't run so many threads without exceeding the operating systems resources. State machines, on the other hand, are very conservative of resources and are very efficient, but harder to program.

Thread programming definitely takes more careful thought than non-threaded code, but it's not so hard to do if you use the right tools. The thread and lock classes in the ZooLib C++ cross-platform application framework are very nice to use.


--

Live your fucking life. Sue someone on the Internet. Write a fucking music player. Like the great man Michael David Crawford has shown us all: Hard work, a strong will to stalk, and a few fries short of a happy meal goes a long way. -- bride of spidy


State Machines (5.00 / 4) (#263)
by moshez on Tue Nov 19, 2002 at 01:27:42 AM EST

Often, in event-based code, it looks as though you need a huge state machine. Don't do it! Big state machines are hard to debug. Instead, write small state machines, translating events to higher and higher level.

Here's an example: SMTP is, basically, a line-based protocol. Thus, an SMTP client first needs to be able to handle random chunks of data, and partition them into lines. This can be done by a fairly simple state machine. Now, SMTP, as far as the client is concerned, always just returns a code. All the continuation lines mess can be hidden behind another state machine, which just looks for non-continuation lines and handles the code. The part that handles the code is a bit tricky to write in the event based way.

SMTP assumes most of the "initiative" comes on the client side. The way I solved this when writing the SMTP client code for Twisted is by having the state be "what we want to do next". Then, when getting a code, the SMTP code handler function called "self.do_<current state>_<code>". If there is no such function, it assumes an error has occured and it terminates the transaction. This allowed me to write only these functions which correspond to a successful sequence, without letting a malicious server lead me through the nose.

I must say, the code ended up being simple and readable, after coming up with that trick!



[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Simple, maybe (none / 0) (#311)
by greenrd on Tue Nov 19, 2002 at 08:37:44 AM EST

I still think it would have been simpler if you'd written it as a sequence of read operations running in a thread. To my mind, sequences of commands are simpler than state machines.


"Capitalism is the absurd belief that the worst of men, for the worst of reasons, will somehow work for the benefit of us all." -- John Maynard Keynes
[ Parent ]

Imatix. (none / 0) (#392)
by Anonymous Hiro on Thu Nov 21, 2002 at 11:45:26 AM EST

IIRC these guys are into state machines - for xitami their webserver and so on.

http://www.imatix.com/html/smt/smt.htm#TOC4
http://www.imatix.com/html/libero/index.htm

Writing an SMTP program in a high level language doesn't seem as difficult - the protocol is fairly simple, and most high level languages allow the chunks of data transparently handled as lines without too many problems. Whether the performance and resource penalty is worth it probably depends on the situation, also the scalability issues may be important.

With a low level language like C half the battle is doing the language right in the first place (otherwise you have those buffer overflow stuff). Rather annoying - it's like a car that needs manual cranking to start and driving a stick shift without a clutch (possible but only experts can do it, and even some experts aren't good enough to do it 100%, see bugtraq). All the "molecular" details which may be relevant for kernel hackers can be rather irrelevant to a SMTP program operating at much higher levels of abstraction.

[ Parent ]

Many threads in a screensaver? (5.00 / 1) (#320)
by caine on Tue Nov 19, 2002 at 09:38:32 AM EST

Since I haven't inquired deeper into your code or thinking about the problem perhaps I've missed something obvious, but why on earth would you want to have threads in something simple as a screensaver? The only thing I can even imagine you'd use them for is to detect user input, but still, that can be solved much nicer.

--

[ Parent ]

Each ripple is a thread (4.00 / 1) (#355)
by MichaelCrawford on Tue Nov 19, 2002 at 07:06:40 PM EST

My raindrops screensaver makes it appear that your monitor is covered with water that rain is falling in. Ripples appear and spread out.

In the multithreaded version, each ripple was a separate thread. It didn't look so good, but you could have over a hundred ripples on the screen simultaneously. But with two hundred ripples, the machine stopped responding. I'm not sure if the OS crashed or the BeOS application server locked up, but in any case you're hosed.

The OS assigned my threads far more resources than they need. I think the problem was the virtual memory assigned for the stack - it runs out of swap space.

In the Mac OS version, it was more complicated to program, but you could run a virtually unlimited number of ripples. Each ripple was a task in a queue, and giving a task a little bit of time would allow it to expand its ripple a little bit.


--

Live your fucking life. Sue someone on the Internet. Write a fucking music player. Like the great man Michael David Crawford has shown us all: Hard work, a strong will to stalk, and a few fries short of a happy meal goes a long way. -- bride of spidy


[ Parent ]

Well... (5.00 / 2) (#359)
by caine on Tue Nov 19, 2002 at 09:36:05 PM EST

Classic case of bad use of threads in my opinion. There's no reason at all why they should be threaded, when you might as well have them in an array and loop through it every now and then. What's your motivation for using threads in this instance?

--

[ Parent ]

smp (4.00 / 2) (#360)
by MichaelCrawford on Tue Nov 19, 2002 at 11:07:14 PM EST

So it would run faster on multiprocessor machines.

What would have been the best thing to do is to have one thread per processor, with each thread handling a state machine like the way the mac version works.


--

Live your fucking life. Sue someone on the Internet. Write a fucking music player. Like the great man Michael David Crawford has shown us all: Hard work, a strong will to stalk, and a few fries short of a happy meal goes a long way. -- bride of spidy


[ Parent ]

Wrong thing to target for SMP (5.00 / 1) (#365)
by caine on Wed Nov 20, 2002 at 07:38:51 AM EST

You won't improve your perfomance significantly even on multiprocessor machines, and it's still totally the wrong approach. It's like trying to do a fibonacci-recursion with a thread for every recursion. And just as with fibonacci numbers, you shouldn't do it like that at all, but instead use a better algorithm (like directly using the formula for fibonacci). There are algorithms for wave-collisions that could update the whole screen in a stroke.

--

[ Parent ]

Threads take more...? (3.00 / 1) (#409)
by sudog on Fri Nov 22, 2002 at 01:13:32 AM EST

Quite the opposite. Event-driven software is far more difficult to write than threads. Why do you think there are so few true event-driven programs out there and that everyone uses threads tor stupid things like reading/writing to disk, updating progress bars, etc? :-)


[ Parent ]
The alternatives (4.80 / 5) (#249)
by carlfish on Mon Nov 18, 2002 at 10:58:12 PM EST

Multiple Processes. This is a pretty robust technique. Additional advantages are that it protects the system from bad programmers, as the processes are generally short-lived enough to make memory leaks less problematic, and a segfaulting child process isn't going to take the whole server down.

On the other hand, fork/exec is pretty heavyweight, and the more data you need to share between the children (for example with a web application, each child may need access to user session data, database connection pools, etc.), you have to deal more and more with the same sort of issues you'll run into with thread programming anyway.

Event-Based Programming You state the limitation of event-based programming right in its description - events must be handled very quickly, or the rest of the program will cease to respond. Some things take a long time. Some things you can't even guess how long it'll take.

Of course, this is why non-blocking IO exists, because it allows you to avoid most things that take a long time (FS and network operations), but what if you're doing something computationally expensive instead? Users do notice if the UI stops redrawing itself while you're encrypting a file, or generating a new key-pair. You have to do a lot of work to make sure long-running code yields back to the event-handler. On the other hand, done properly, it's pretty safe to combine event-based programming with multiple threads.

Java's Swing library provides an example of a safe combination of event-handling and threading. The Swing UI has a single-threaded event model. This ensures, for example, that the contents of a window don't change halfway through a repaint. In badly-coded Swing applications, it's really, really common to have the whole app stop responding (including the UI repaints) because an event is taking a little longer to handle than expected, leaving you with a grey blob instead of a window. The proper approach is, if your event-handler may not return promptly, to spin off a thread in which to perform the action so the event-handling thread can go on its merry way. So long as your thread doesn't touch the UI, you won't cause any concurrency problems.

But, you say, what about when the thread has finished, and I want to update the UI to display the results of my calculation? There are methods on the SwingUtilities class to allow you to place code into the event queue, and have it executed by the Swing event-handling thread, safe from concurrency issues.

This is a pretty common threading pattern. I used it myself quite often. If you have a program that would benefit from threads, but you're worried about the concurrency being dangerous for another part of the system, have the multi-threaded part of the system post any potentially problematic events onto an event queue, and then have a single thread running through the queue.

Co-operative Threading would be truly ugly. You'd not only have to pepper your code with a disfiguring number of yield() statements, you'd have to trust third-party libraries to do the same. At least with pre-emptive threading, if a library isn't thread-safe you can wrap it and protect it from the multi-threaded world. In co-operative threading, a non-thread-safe library would freeze the whole app while you wait for it to return control to the thread-aware, yield()ing part of the code. Eugh.

Charles Miller


The more I learn about the Internet, the more amazed I am that it works at all.
Computationally intensive? (3.00 / 3) (#254)
by pla on Mon Nov 18, 2002 at 11:28:55 PM EST

Much of the code I've done (outside work, I never do anything *NEARLY* so cool at work) involves fairly CPU-intensive data analysis or production. I can confidently say that you do NOT need multithreading to perform such tasks and still update the program's GUI.

I will agree that the Windows' standard callback model tends to fail in such situations (I suspect this makes the single most common cause of Windows saying an application has "stopped responding").

The "easiest" solution involves simply having the "WM_PAINT" handler a separate function, which you can call manually from the CPU intensive function every so often (yes Virginia, you can call GetDC() outside the callback).


As for your comment about cooperative multithreading, do you honestly find "ordinary" multithreading any easier to read? And as for libraries - you place the same level of trust in them any time you have multiple execution points in one process space anyway - At least using cooperative multithreading, you know the library won't clobber you at any random point in time (it will happen at very specific times <G>).

Basically, in all my years coding, I have yet to find a situation where threads would have come in handy. Need multiple execution points? Spawn another process. Communication? Pipes and shared memory work great, depending on what you need, without giving each process the ability to crash the other. If a pair of processes *really* have such a dependance on one another that they need to run in the same memory space, I find they also have such an intense serial dependance on one another that you gain nothing by using two separate tasks. And, as a sort of aside, even if they *don't* have strong serial dependance, it doesn't matter because in practice, threads don't ever execute simultaneously on separate CPUs anyway, whereas distinct processes will.

On the other hand, I have spent quite a few nights trying to fix a tough-to-reproduce bug caused entirely because someone just *had* to use multiple tasks for no good reason.


[ Parent ]
Threads not simultaneous? (5.00 / 1) (#308)
by greenrd on Tue Nov 19, 2002 at 08:30:23 AM EST

because in practice, threads don't ever execute simultaneously on separate CPUs anyway, whereas distinct processes will.

What do you mean? Why not?


"Capitalism is the absurd belief that the worst of men, for the worst of reasons, will somehow work for the benefit of us all." -- John Maynard Keynes
[ Parent ]

Because Many Operating Constrain It That Way (2.50 / 2) (#310)
by moshez on Tue Nov 19, 2002 at 08:36:48 AM EST

I'm not sure why -- but if you write a patch for Linux which lets it run threads on different CPUs with no performance loss, I'm sure you will not turned down.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Maybe in 1995... (4.50 / 2) (#330)
by dmcnaught on Tue Nov 19, 2002 at 11:21:38 AM EST

Threads in Linux are (and have been for a long time) separate tasks from the kernel perspective. They will run simultaneously just fine on multiple CPUs.

Not that I disagree in general with your thesis, but you're way out of date on this one...

[ Parent ]

SMP and threading (4.00 / 1) (#361)
by Znork on Wed Nov 20, 2002 at 03:36:05 AM EST

The most obvious reason to have strong cpu affinity for related threads is that if you dont you're going to invalidate your cache as soon as a thread accesses some shared memory. If you have a threaded program which does a lot of shared accesses the cache invalidations will kill off any benefit from SMP.

[ Parent ]
On having half a clue. (none / 0) (#427)
by carlfish on Thu Nov 28, 2002 at 06:42:25 PM EST

Ah, so your solution is to stick windowing-system callbacks in your calculation code? A problem that could be solved perfectly safely by running it in a seperate thread (no code-sharing, and the only locking issue being the point at which the thread delivers its results back to the UI) is instead solved by polluting bits of your code that shouldn't have to even know there is a UI.

There are places where, for the sake of the rest of the program, you have to break abstraction. But when you can safely use threads, and retain abstraction, I say go for it. It's easy to recognise the cases in which you can do it safely, and where you have to watch out and maybe use something else. You just have to exercise half a clue. I'm sorry if exercising half a clue is beyond some programmers, but if I started throwing techniques out of my programming toolbox whenever some people can't be trusted with them, I'd probably end up in some dungeon writing COBOL.

Cooperative multithreading is pure evil. You think that regular multithreading gives you the risk of deadlocks? Normally, you only have to worry about deadlocks with critical sections of code. With cooperative multithreading, every section of code is locked, which raises the risk of deadlocks by orders of magnitude. An example from the cooperative multitasking world - Internet Explorer for the Classic MacOS doesn't yield at certain points during downloads... which is great right up until you try to run a webserver on localhost. Instant deadlock.

Charles Miller


The more I learn about the Internet, the more amazed I am that it works at all.
[ Parent ]
Why not combine a few? (5.00 / 3) (#255)
by Kal on Mon Nov 18, 2002 at 11:48:20 PM EST

At work we've got a nice distributed system using a mix of multiple processes and event driven programming.  

We've got processes spread out across half a dozen machines communicating with TCP, broadcast, and multicast (obviously the multiple processes part).  The processes can send messages back and forth without having to worry about how long it's going to take them to finish a task because the sender doesn't have to sit and wait for a response.  The sockets wake up when a message is recieved and the process routes it to the correct portion of code internally (the event driven portion).  

I think this is quite a nice architecture because it allows us to sperate the computationaly intensive code from the code that needs to be fast, as in a display, and allows everything to talk together without having to sit around waiting  for data.

[ Parent ]

Fork/Exec And Other Myths (5.00 / 5) (#304)
by moshez on Tue Nov 19, 2002 at 07:24:00 AM EST

On the other hand, fork/exec is pretty heavyweight
Just to clear up one point of confusion: you can fork without exec. This is a pretty lightweight operation on UNIXes, unless you have an '80s relic. On modern UNIXes, fork() does COW (Copy-On-Write) which means all fork() really copies is just the process tables, which are usually in the 2-3kbyte area. Of course, any data the child modifies has to be copied, but this is usually a very small subset, especially if the child does some specialized task which, for the most part, doesn't require touching variables.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Minor nit: (none / 0) (#425)
by harik on Mon Nov 25, 2002 at 09:51:29 PM EST

On modern UNIXes, fork() does COW ... Of course, any data the child modifies has to be copied.

You forget that the entire memory space is now CoW for the parent as well, so it's a loss if your parent is working on large sections of memory while the child deals with very few.

[ Parent ]

Question (3.00 / 3) (#251)
by Big Sexxy Joe on Mon Nov 18, 2002 at 11:01:02 PM EST

If you write a Windows program with child windows, are the child windows threads, other processes, or something else?

I'm like Jesus, only better.
Democracy Now! - your daily, uncensored, corporate-free grassroots news hour
something else (5.00 / 1) (#258)
by NFW on Tue Nov 19, 2002 at 12:20:36 AM EST

The Windows UI is event-based.


--
Got birds?


[ Parent ]

Here's one for you (5.00 / 5) (#253)
by celeriac on Mon Nov 18, 2002 at 11:23:31 PM EST

Suppose the output of program A is piped into program B. Program A is bound by the network or a slow piece of hardware (say, cdparanoia), while program B is CPU-intensive (say, LAME or oggenc). How can you achieve the greatest throughput between A and B?

It surprised me a little bit that piping the output of cdparanoia to LAME is no faster, and can often be slower, than writing the output of cdparanoia to a temporary file and then running LAME on the file. But the nature of a UNIX pipe is that once program A writes to the output, it is blocked until program B has read the output and requested more. So cdparanoia reads a sector, then LAME encodes it, while cdparanoia sits idle; then LAME sits idle while cdparanoia reads the next sector (and cdparanoia often has to wait for the disc to spin around again because it was paused in the middle of reading, which makes things even slower). The two processes trade off, resources are underutilized, and it takes forever to encode your CD collection.

My solution to this was a small program 'buffer' which has two threads, one reading from standard input into a memory buffer, and the other writing from the memory buffer to standard output. So I would sit 'buffer' in the middle, as in "cdparanoia | buffer | lame", and in the ideal case (on a machine where ripping and encoding take the same amount of time) the throughput is nearly doubled.

What would be a non-threading solution to this problem?

Not really for this application (5.00 / 1) (#256)
by Kal on Mon Nov 18, 2002 at 11:55:46 PM EST

It's not really an answer for this application, where I doubt you are able to rewrite either of the programs, but I'd use sockets.  Program A streams across a socket to program B.  Program B will get data and start to process it.  While it's doing the processesing program A continues to stream but the socket on the other side buffers the data until B can handle it.  Plus, if A and B are on the same machine the data never even makes it to the wire.

It's been a while so I'd have to look it up to be sure, but I beleive that it's possible to do the same thing using pipes.  Not the shell pipes that you used in your example which is just taking stdout from one program and sticking it in stdin in another, but actual pipes.

[ Parent ]

Not with pipes (none / 0) (#285)
by celeriac on Tue Nov 19, 2002 at 03:04:52 AM EST

I'm pretty sure the named ("real?") pipe you get with pipe() or mkfifo has the same blocking problem. Bash connects processes with real pipes, too, at least according to lsof.

[ Parent ]
Some Terminology (none / 0) (#286)
by moshez on Tue Nov 19, 2002 at 03:09:35 AM EST

pipe() generates an "anonymous pipe". mkfifo creates a named pipe. From the point of view of a writer or a reader there is not much difference. The big difference comes from concerns such as lifetime and the ability to have readers and writers which are not "related" (where you take parent/child literally :) Neither of these are more "real" than the other, and how they show up in lsof is implementation dependent. (For example, an implementation, AFAIK, is free to implement pipe by opening a temporary fifo for reading and writing).

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
I Forgot to Add (5.00 / 1) (#300)
by moshez on Tue Nov 19, 2002 at 07:05:14 AM EST

Reading from a pipe which has no data waiting will block (select() will not signal such a pipe as ready for reading). Writing to a pipe works, until some fixed-sized buffer the OS keeps gets full, and then it blocks. Again, select() will not signal such a pipe as ready for writing. "|" in shell usually does something along "create pipe;fork;in parent, close reading end;in child close writing end;in child execute process". Two pipes naturally cause this sequence twice. Of course, variations on this theme are numerous.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Event-Based Solution (5.00 / 6) (#257)
by moshez on Tue Nov 19, 2002 at 12:18:45 AM EST

Look ma, no locks!

Have a simple select() which checks for readability on the standard input. If there is data, also check for writability on standard input. Read if it's readable. Write if it's writable. If you have no data, don't check for writability. In all cases, the select should be without time limit.



[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Cool (4.50 / 2) (#281)
by celeriac on Tue Nov 19, 2002 at 02:49:22 AM EST

I hadn't come across select() before. It looks ideal for this application.

I have another question which comes up in audio/multimedia. Suppose you have the a process that is generating audio and runs in real time--it has a task is has to do, and it needs to meet latency deadlines and deliver a chunk of audio at regular intervals. Naturally this application needs to have a graphical user interface of some kind, but that's a good case for having a backend and frontend communicating through sockets. Now, what if you also want to be able to load plugins or samples on the fly? I.E., the backend needs to be able to have files read into memory and have memory allocated for data structures, all while not missing its deadlines for data processing. The reads and mallocs should be able to take arbitrarily long, with the idea that the program will keep delivering audio samples while waiting for its data. One approach could be to have two threads, one which sets up data structures and does things that can take unpredictable amounts of time, and another that is responsible for the audio production and is set SCHED_FIFO (it gets a bit sketchy here--I'm not sure what the support is across Unices for having threads with different scheduler priorities). Is there a good way to do this without threads?

[ Parent ]

Again (5.00 / 1) (#283)
by moshez on Tue Nov 19, 2002 at 03:02:07 AM EST

I'd have the thing which needs to be done in realtime do its thing in its own process, and do the rest of the stuff in a seperate process. Often, you need this anyway -- to have any hope of meeting deadlines, you'd want to give the realtime part higher priority. I wouldn't count on wide support for giving threads different priorities (Linux might, but I'm not sure how it would work on Solaris. It's certainly not documented in the POSIX threads standard)

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
nope (none / 0) (#403)
by klash on Thu Nov 21, 2002 at 06:35:25 PM EST

You missed the point. The process doing the realtime thing (reading/writing to the sound card) needs to be able to do non-realtime things like disk i/o and malloc, because it needs to be able to send data from the disk to the sound card. And there's no chance of putting the disk i/o stuff in a separate process, because then you'd be sending all of the audio data over a pipe, and the realtime process would have to block-on-read from the pipe anyway, which is quite a non-realtime thing to do.

And trying to find a way to "solve" this "problem" is pointless, because there is no problem. FIFO buffers can be written that exchange data between threads with no locking required.

[ Parent ]

Unix pipes don't automatically buffer? Bummer. (none / 0) (#400)
by Sloppy on Thu Nov 21, 2002 at 05:24:58 PM EST

But the nature of a UNIX pipe is writing the output of cdparanoia to a temporary file and then running LAME on the file. But the nature of a UNIX pipe is that once program A writes to the output, it is blocked until program B has read the output and requested more. So cdparanoia reads a sector, then LAME encodes it, while cdparanoia sits idle
*gasp*
What would be a non-threading solution to this problem?
One non-threading solution is to run an OS whose pipes can do that buffering for you, such as AmigaOS. Then you don't need to write that buffer program at all. :-)

For example, you'd run "cdparanoia -o pipe:foo/8000000" to start ripping to an eight million byte buffered pipe, and "oggenc pipe:foo" to read and encode it. cdparanoia wouldn't block until oggenc had fallen 8 meg behind.

Of course, since we're talking about AmigaOS, your CPU is a 68k instead of an Athlon, so oggenc will take all day and the buffer will fill and cdparanoia will block anyway. ;-)
"RSA, 2048, seeks sexy young entropic lover, for several clock cycles of prime passion..."
[ Parent ]

They do but the buffer is fixed (none / 0) (#401)
by quvatlh on Thu Nov 21, 2002 at 05:59:54 PM EST

Most Unixen limit the pipe to 4K (some 8K).
This app is most likely writing more than the buffer will hold and so blocking.

His solution permits him to alter the buffer size to suit his needs. Nice job.

It is, however, not necessary to use threads. A select loop (see next post) will do the trick just as well.

[ Parent ]

Apache ain't your poster-boy (4.00 / 2) (#270)
by lorcha on Tue Nov 19, 2002 at 01:48:00 AM EST

While Apache 1.x is multiprocess, Apache 2.x has switched to a multiprocess/multithread hybrid approach to increase scalability.

Feel free to check it out here.

--
צדק--אין ערבים, אין פיגועים

Well, Yes (5.00 / 1) (#273)
by moshez on Tue Nov 19, 2002 at 01:59:35 AM EST

But still, most people use Apache, even Apache2, in a threadless configuration.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Again, Well, Yes (5.00 / 2) (#278)
by lorcha on Tue Nov 19, 2002 at 02:23:29 AM EST

But the folks at Apache must have found some value in threads if they rearchitected the server to use them.

Anyway, why are you so biased against threads? As others have pointed out, the three bad scenarios you point out: race conditions, deadlock, and starvation, are equally likely to happen in multiprocess situations. Sendmail pratically invented the race condition and starvation/deadlock make me think of nasty db locking in RDBMSs. Why do you believe multiprocess apps are less prone to these problems? What about orphaned threads versus zombified processes? What's the difference? They are just as easy to create if you're not careful.

Methinks the real problem is inexperienced developers writing non-QAed code.

--
צדק--אין ערבים, אין פיגועים
[ Parent ]

The Story is More Complicated (4.33 / 3) (#279)
by moshez on Tue Nov 19, 2002 at 02:29:44 AM EST

But the bottom line is that the straw that broke the Camel's back was that to be portable to Windows they had to have threads, and they wanted to keep everything in one code based (a sensible decision from a QA perspective). Note that Apache was never a high-performance server -- Apache's gimmick was, and is still now, flexibility.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Processes == OS threads (5.00 / 1) (#331)
by gregbillock on Tue Nov 19, 2002 at 11:27:06 AM EST

I agree; ultimately, processes are threads managed by the OS. (OK, OK, if you have a connection machine, that isn't entirely true... :-)) So for every problem with threads there will be a corresponding problem with multi-process apps.

I guess the response would be that OS programmers are disciplined enough to handle the responsibility.

[ Parent ]

no... (none / 0) (#332)
by avdi on Tue Nov 19, 2002 at 11:29:30 AM EST

Threads are threads managed by the OS.  Processes are processes managed by the OS.  Userland threads aren't managerd by the OS, but I rather doubt that's what the article is talking about.

Threads share a single address space.  Processes have seperate address space.  Both are managed by the OS.

--
Now leave us, and take your fish with you. - Faramir
[ Parent ]

AND vs OR Parallelism (4.80 / 5) (#289)
by Baldrson on Tue Nov 19, 2002 at 04:14:23 AM EST

Check out Mozart's light-weight threads with logic variables if you want a clue as to the real semantics behind threads and why they are not simply useful but almost an inevitable consequence of good design in most any application.

The basic issue is one of what logic programmers call "AND" vs "OR" parallelism. AND parallelism results when you need to merge multiple independenly evaluable streams of results during a functional composition. OR parallelism results when you need alternatives tested within some context and those alternatives are all sufficiently likely to allow them to be tested in parallel (and since the general decidability of termination is not possible with Turing machine equivalents, such parallelism is necessary).

-------- Empty the Cities --------


Dear God, why oh why.... (4.80 / 5) (#292)
by S1ack3rThanThou on Tue Nov 19, 2002 at 05:25:32 AM EST

Did you not write this before I went to uni 6 years ago?

Dammit I've inherited a project with an unknown number of threads, dynamically generated with EXACTLY the list of problems you describe. It also has exactly the solutions tried that you describe.

Excellent article that we should ALL learn from, or else suffer the extremely painful consequences.

Why didn't anyone tell me threads were as dangerous as GOTO!?

"Remember what the dormouse said, feed your head..."

The Original Motivation (5.00 / 4) (#299)
by moshez on Tue Nov 19, 2002 at 06:53:31 AM EST

The original motivation for this article is that we (the Twisted development team) wanted a canned answer for the next time someone asked us why threads are bad. Fortunately, with k5, now we can point such people at the K5 discussion and say "if you don't have an argument which has *not* been done to death in the K5 discussion, please don't even bother arguing".

K5: Because Flamewars Should Be Archived, Not Repeated.



[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Cannot agree with this more (4.66 / 6) (#306)
by localroger on Tue Nov 19, 2002 at 07:36:24 AM EST

Ironically I am reading this on a customer's computer as I prepare to upgrade a massive system of interlocked network processes. Each one, including the server, is single-threaded by design -- for exactly the reasons you cite.

Of course this causes some problems, mainly due to Windows' shitty API. I am using a big loop for the server to poll the client TCP sockets for incoming data and process one request at a time. And it bogs down, even on a 2 GHz machien with half a gigabyte of RAM.

Now somewhere in the memory of this machine is a long integer for each of those sockets that I should be able to inspect to see if any data has come in. It should take less than a microsecond to do that check, but NOOOOOO. I suppose it isn't even Microsoft's fault that the Select() function is such a piece of crap, but when they extended it they made it worse. The recommended solution to this is to use the Windows messaging system.

Except the Windows messaging system can only notify you of events on 64 TCP sockets per thread. So the recommended solution is to spawn enough threads to handle as many sockets as you need, but not one thread per socket because that will cause performance to bog too.

That's bad enough, but any program with a user interface can be frozen by certain user actions, and the recommended cure for this is, again, to do the real work in a thread that doesn't own any windows. Finally, all those threads do bog performance (every context switch adds overhead) so the recommended way to deal with this is, get this, to spawn a thread, set up a bunch of events, and then call an API that freezes the thread until an event occurs.

All this to do something which should really be no more complicated than polling a few long integers. It's a wonder computers work at all. Really.

I can haz blog!

Are you sure about that? (5.00 / 2) (#356)
by Kalani on Tue Nov 19, 2002 at 08:26:38 PM EST

Why don't you just use WSACreateEvent/WSAEventSelect/WaitForMultipleObjects? That way you get the benefit of letting the OS ignore your process until it has something to do, and you can take out your polling method. Doing continuous polling in and of itself can bog down your system because the scheduler in Windows will increase your priority if it thinks you really need the CPU (when every other process gets 0% CPU time because they're waiting for input, and you're taking over the idle process's role at 99%, the OS takes that time away from the idle process [which is what decides whether or not to wake up other processes in the first place]).

With the event method you'll want to add another thread or another process if you want a responsive UI. If you find that method distasteful, why don't you just use the WSA overlapped I/O routines? It'll preserve your single-threaded model and you get the nice benefit of having the socket library write directly to your provided buffers rather than copying everything as it comes in (which can really improve performance when you get a lot of simultaneous connections).

-----
"Images containing sufficiently large skin-colored groups of possible limbs are reported as potentially containing naked people."
-- [ Parent ]
65-socket limit (5.00 / 1) (#367)
by localroger on Wed Nov 20, 2002 at 08:11:37 AM EST

The wsa event routines can only notify you of events on 64 sockets per thread (at least in Windows NT, which is the platform all of this is targeting). This morning the server was reporting 62 connections and that's with the system less than half-completed.

The method I finally settled on after testing a lot of alternatives is to use a timer event to drive the polling. Each timer tick triggers a single round-robin and check for new connections. The service routines are also written to break up large tasks so they are done in pieces. The goal was ~1/2 second response since there are some real-time client UI's that depend on server access, and the system maintains that very nicely, even when someone else requests a quarter-megabyte report.

There is a typical delay of 1/17 second between the end of one poll and the start of the next because of the coarseness of the system timer, but that isn't a problem in this system. These dropouts keep the polling loop from seizing the entire CPU, and they keep the UI moderately responsive. The server still freezes if someone left-clicks the title bar and doesn't move the mouse (try it sometime) but at least it reliably wakes up when the event lock goes away.

Meanwhile, I don't need a lot of kludgey code or a separate thread to deal with the fact that Windows loses events all over the place if a thread is locked by UI events. I first noticed this with regard to serial port operations, but it holds for wsa event notifications too; if you're grabbing the title bar and holding it, or locked in a code loop when the event occurs, the event is lost.

I can haz blog!
[ Parent ]

Well (2.50 / 2) (#368)
by Kalani on Wed Nov 20, 2002 at 08:49:00 AM EST

I couldn't find anything in my documentation that describes this limit, and I think I've got an application running here that has had more than 65 connections on the one server thread. Did you get this number somewhere in the MSDN library?

Also, I think that your situation with the timer could be a problem. 0.5 second response times could actually get bumped up to around 1 full second if you have some cycles where it takes you too long to process the WM_TIMER message (keeping Windows from posting the next one on time and making you skip a cycle). Also, the system timer on a PC has a resolution of 10 ms right? Where's the 1/17 s coming from (sorry if I don't understand that part)?

It sounds like you've already got some funky UI behavior. If the WSAEventSelect method is limited to 65 sockets, why don't you just use overlapped I/O? That's actually the technique that they suggest for server programs anyway. Plus you won't have to do any polling at all that way. Just read from your own buffers (page-aligned if you want to make it as efficient as possible) when you get I/O completion notifications. Just use WSAIoctl to make your newly accepted sockets operate in overlapped mode.

In any case, if the server you're writing does wind up being used by 200 simultaneous users or more, you might want to think about using a connection pooling technique. You can get faster response times for each client program if you have a limited number of sockets per thread on the server (although they might not get all their data in one pass -- so you might only want to use that technique if it makes sense to have partial information from a network request).

-----
"Images containing sufficiently large skin-colored groups of possible limbs are reported as potentially containing naked people."
-- [ Parent ]
Limits (5.00 / 1) (#371)
by localroger on Wed Nov 20, 2002 at 09:51:40 AM EST

Here is a discussion of the socket limitations with different techniques. Note section 4.9. This behavior is also noted in the Winsock 2.0 API documentation.

Response time / 1/17th sec: I actually have the timer set for 10 msec, which means (I have tested this) that it actually triggers every 55 msec, which is the rate of the basic un-reprogrammed PC clock timer interrupt. In DOS you can reprogram the timer chip, but of course this is "not recommended" in Windows :-) I expect to miss clock ticks. You cannot really build a Windows application that doesn't miss clock ticks, or any other type of event, without dedicating a thread and using the WaitForxxxxEvent() API's. But at least with the timer event, if you miss a clock tick there's always another one coming along; if you miss a serial port or socket event, the system is stuck unless you do reality-check polls occasionally; and if you've got to poll anyway, why dance? The point of using the timer as I am is to periodically hand control back to Windows so the OS can take care of business and won't let me hog the CPU, but to also be able to depend on getting it back in a timely manner so I can service the next round of client traffic.

The reason my latency extends to the half-second range is because of of disk accesses. The database will eventually be in the 1 to 2 gigabyte range (full info archived for about 2 million boxes of product). The rate at which information is collected is modest (server reporting about 3K/sec right now) but the file structure being built is quite large.

I did look into using IOCP, as this is clearly the way Microsoft wants you to do this sort of app. Unfortunately the whole idea seems meant to be implemented not only with separate threads for process and UI, but with a pool of threads which are given jobs by an algorithm I really don't feel like debugging at 3:00 AM in a closet above the kill floor of a chicken plant.

Fortunately this project is not on par with, say, a webserver which must handle thousands of more or less random requests. I have some control over the traffic, and while there are both large and small transactions it is only the small ones which must be completed in relatively quick fashion. Most traffic is also sporadic so the worst-case latencies are rarely seen. I have achieved the performance I need with code which is very, very simple and easy to debug; everything can be run in the VS IDE and there are no thread synchronization problems to deal with at all.

I can haz blog!
[ Parent ]

the timer (none / 0) (#375)
by pb on Wed Nov 20, 2002 at 11:53:42 AM EST

Although you might not need it, Windows also has the high-performance timer, which is easy to use and can have much better resolution.  I used them to get a ping program working in Windows that could accurately display times of > 1ms for some network testing...

It also has "multimedia timers", which apparently can provide callbacks--I haven't touched these at all.
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]

HP timers (4.00 / 1) (#378)
by localroger on Wed Nov 20, 2002 at 01:17:31 PM EST

As the page you link mentions, HP timing requires add-on hardware. The most common hardware capable of supporting this is a sound card. In a plain PC, you can read the timer value to higher resolution by reading the value of the counter that drives the INT 08 interrupt, but you cannot actually create dependable events more often than every 55 msec.

I can haz blog!
[ Parent ]

huh? (none / 0) (#383)
by pb on Wed Nov 20, 2002 at 08:05:17 PM EST

The page I link to says no such thing; I've used the high-performance timers in Windows--they certainly have finer than 55 ms resolution, and I've seen nothing implying that any add-on hardware is required (although all the machines I was using had sound cards); maybe you're thinking of the multimedia timers?

Also, I don't know what you're talking about when you say "dependable events", as all of my timing was going on in a loop, but I assure you that I can reliably time events with resolutions of less than a millisecond in Windows--in my case, pings.

Now you can either find a link substantiating your claims, or you can write a test program using the calls mentioned in my link and figure it out for yourself.  I was trying to help, as I have used better timers in Windows than you have, apparently--but if you don't want anyone to help you, that's fine too.
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]

Timer hardware (none / 0) (#389)
by localroger on Thu Nov 21, 2002 at 07:43:52 AM EST

From the page you linked:

Note that the existence of a high performance timer is completely dependent on hardware. If QueryPerformanceCounter() returns false, then no high performance timer exists on the system.

The standard timer in a PC consists of a counter which fires INT 08 every 55 msec. In DOS you can reprogram this timer to fire the interrupt more often, but you can't do this under Windows without hosing the OS. In a loop it is possible to read the value of the counter and measure time intervals smaller than 55 msec, but in Windows you cannot get interrupts more often than that, which means you cannot reliably force a context switch more often than that if another thread is hung.

What this means is that if you crank up Visual Basic and set a Timer1.Interval=10, it will fire every 55 msec. If you set Timer1.Interval=40 it will fire every 55 msec. If you set Timer1.Interval=60 it will fire every 110 msec. And so on. There is no way around this with the standard Windows timer API.

Add-on HP timer events which can actually call Timer1_Timer more often than every 55 msec are depending on add-on hardware like a sound card to do it. The basic PC is not capable of interrupting a hung process to force a context switch more often than this, although you can measure short time intervals if you stay in a loop and read the value of the counter repeatedly.

I can haz blog!
[ Parent ]

ok, so... (none / 0) (#390)
by pb on Thu Nov 21, 2002 at 08:53:28 AM EST

If you want to use threads with this, maybe you can use the multimedia timers instead--I haven't tried them.  But you're still wrong about the add-on hardware.  And yes, I was just staying in a loop, and calling the high-performance timer functions as needed--worked great for what I was doing.
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
Wrong about what? (none / 0) (#391)
by localroger on Thu Nov 21, 2002 at 09:48:31 AM EST

The link you give does not contradict anything I said. The multimedia timer functions are using the sound card -- that's why they are called multimedia timers, they don't exist on systems that don't have multimedia hardware. It is especially common in industry to see PC's without this hardware, although I expect that to change as more motherboards come with sound hardware built on.

I can haz blog!
[ Parent ]

*sigh* (none / 0) (#393)
by pb on Thu Nov 21, 2002 at 12:07:27 PM EST

Maybe they do and maybe they don't, but the High-Performance Timers do not use the sound card--and those are the timers I've used, and the timers that I was suggesting you might want to try using.
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
IIRC (none / 0) (#439)
by BCoates on Wed Dec 04, 2002 at 02:39:13 PM EST

The QueryPerformanceCounter() is supposed to use the RDTSC instruction on pentiums.

I've used it on systems with no sound card myself, it still worked for me.

--
Benjamin Coates

[ Parent ]

Interesting page (none / 0) (#380)
by Kalani on Wed Nov 20, 2002 at 02:32:11 PM EST

Yes now I remember the issue with WaitForMultipleObjects. I think that I had to write a stupid hack around that by having N threads for N * 65 sockets where each thread would just signal a single event that indicated that some event was raised for one of the sockets across all those threads (then when you catch the event you look at some shared variable that indicates which one raised the event -- so obviously you have to synchronize that thing). Overall I think that's more trouble than necessary (although you can at least generalize it to always get around the 65 event limitation for other purposes).

What that page did not address is problems with overlapped I/O. In fact, you could still use overlapped I/O to get rid of the polling code in your program and still have a single-threaded server (so you can also run everything in the VS IDE, etc). You'd reduce the (admittedly fairly small) latencies introduced with the timer and the server UI would run more smoothly. With overlapped I/O you can just provide a callback function to be notified when some event on the associated socket is raised. I guess you could also use the WSAAsyncSelect function to get Windows to send messages to some window's message queue if you'd rather handle it that way (although you'd have to copy received data into your own buffer which could become fairly inefficient if you ever do any really significant data transfers over a high speed network). Either way, you get single-threaded socket event notification and you're not limited by the number of event objects that WaitForMultipleObjects can manage at one time.

-----
"Images containing sufficiently large skin-colored groups of possible limbs are reported as potentially containing naked people."
-- [ Parent ]
That may be next (none / 0) (#381)
by localroger on Wed Nov 20, 2002 at 03:25:25 PM EST

In fact, you could still use overlapped I/O to get rid of the polling code in your program and still have a single-threaded server (so you can also run everything in the VS IDE, etc). You'd reduce the (admittedly fairly small) latencies introduced with the timer and the server UI would run more smoothly.

Yes, when I get everything working satisfactorily I will probably look into doing it this way. I am using overlapped I/O for the serial ports (a lot of the clients are bridges to RS-232 devices) but I had mixed success quickly hacking similar code into the server TCP/IP handler. Once the whole thing is implemented I do plan to look into improving efficiencies.

The thing is working quite well all things considered. There are a number of stations where the sequence of events is something like this:

User hits F1 at scale
Scale to PC over RS232: "He hit F1, what do I do?"
Client sends a request to the server
Server sends back the info
Client tells the scale "put up this message"

There are currently 22 stations of this type, and another 25 bots which are more automatic (the machines keep running whether the PC is up or not, and the data are echoed to the database) and a dozen or so humans who might request various reports from the system, and the performance of the terminals is very acceptable.

I can haz blog!
[ Parent ]

VS IDE (none / 0) (#386)
by DodgyGeezer on Thu Nov 21, 2002 at 12:53:23 AM EST

I routinely debug multi-threaded apps in the VS IDE (MSVC6 & VS.NET)... I haven't seen any problems that would make me need a single-threaded process.  What made you claim this?

[ Parent ]
I think you're mixing up the claim (none / 0) (#394)
by Kalani on Thu Nov 21, 2002 at 12:08:48 PM EST

localroger said that he didn't want to use multithreaded techniques because, among other things, it's hard for him to debug in the VS IDE. I haven't had a problem with debugging multiple threads in the IDE either, but it may be difficult for him for some other reason ... I just accepted that he'd found this to be a valid concern for him. I was not making a claim about the difficulty of debugging programs in any way.

-----
"Images containing sufficiently large skin-colored groups of possible limbs are reported as potentially containing naked people."
-- [ Parent ]
Hidden Window (3.00 / 2) (#373)
by DodgyGeezer on Wed Nov 20, 2002 at 10:20:32 AM EST

Why not just create a hidden window?  It's not kludgey - it's a pretty standard approach.  Communicate with your separate thread with PostMessage and SendMessage.  This way the UI will never block your IO.

[ Parent ]
windows' crappy select() (2.00 / 2) (#377)
by BCoates on Wed Nov 20, 2002 at 12:29:36 PM EST

part of the reason select() sucks so much in windows is the FD_SET() macro in winsock2.h--It scans over the fd_set array for every fd you add, looking for duplicates.  If you already have a list of unique sockets to select on, you can copy it into the structure yourself and save a lot of time if you're selecting on a large number.

--
Benjamin Coates

[ Parent ]

Threads and real-time apps (4.83 / 6) (#319)
by cyberlife on Tue Nov 19, 2002 at 09:37:12 AM EST

Recently, I was working on a real-time networked audio system. The #1 problem during testing was latency -- many packets were being dropped as they were arriving too late.

During design, a threaded model was chosen as being the most logical. However, on a hunch, I replaced the threaded system with a single, select() based loop. The latency disappeared. Further experimentation confirmed that the overhead of switching threads (especially in a UNIX environment where true threads don't really exist) was introducing the delays into the system.

Even under high-load conditions, network applications often spend much of their time asleep, as CPUs are significantly faster than NICs. Performing a context-switch every time a byte comes in (or worse, waking a thread only to see there's nothing to do and going back to sleep) is a major waste of resources.

If you're programming under UNIX, learn to use select(). It'll be your friend in many situations and save your ass countless times.

select() doesn't always work (4.66 / 3) (#358)
by Pseudonym on Tue Nov 19, 2002 at 08:33:33 PM EST

The select() call is great if you're polling one or more file descriptors. However, it's very common that you want to do something when a file descriptor or some other condition occurs, and you must not burn CPU cycles in a busy loop while you're waiting.

Unfortunately, the POSIX interface is broken here, as there is no way to wait on both a file descriptor and a condition variable at the same time. You either need to introduce another thread, or be tricky with signals.



sub f{($f)=@_;print"$f(q{$f});";}f(q{sub f{($f)=@_;print"$f(q{$f});";}f});
[ Parent ]
Not entirely accurate.. (none / 0) (#407)
by sudog on Fri Nov 22, 2002 at 12:53:05 AM EST

Why should a variable change from outside the scope of the actual program? And if it does change from outside the program, what's stopping whatever changed that variable from delivering a signal at the same time? Laziness man, you're compensating for pure laziness!

The POSIX interface is not broken. You're trying to say that watchpoints should be part of the select() interface? Ha ha, that's funny.


[ Parent ]

Laziness?! (4.00 / 1) (#415)
by Pseudonym on Fri Nov 22, 2002 at 06:58:31 AM EST

I'm a Haskell programmer, dude. Laziness is a virtue. :-)

Why should a variable change from outside the scope of the program? Well, it might not be from outside. Consider, for example, a program where you are waiting on either a file descriptor or a GUI event. The GUI event probably comes from inside a toolkit, outside your control, so you can't rewrite it to deliver a signal.

I'm not saying that watchpoints should be part of select(). I fear that select() is beyond redemption as it is without adding even more cruft.

What POSIX could do with is a unified model of "events", whether that event be from a datagram socket, a file descriptor, synchronisation primitives (condition variables, semaphores, whatever), IPC or signals. Win32 has something like it, but its interface is kinda clunky. QNX pulses are probably what I really want.



sub f{($f)=@_;print"$f(q{$f});";}f(q{sub f{($f)=@_;print"$f(q{$f});";}f});
[ Parent ]
Unified model of events unrealistic.. (none / 0) (#444)
by sudog on Wed Dec 11, 2002 at 02:46:14 AM EST

Tell you what--you design the hardware that better supports your idea of what the system should support and it'll be a lot easier to redesign things in your own image.

There's a reason why select() is for i/o.

And some machines don't do watchpoints. In fact, if I recall correctly there are many environments that don't even support breakpoints.

So why include an interface for them in a system that still has to support these older machineries when select() works as well as it does already?

In fact, if you're using a toolkit that uses signals and the like to monkey around with events, then find a way to generate events using the select() loop. Open things up a little: what's easier--reworking the select() interface because you think it's inconvenient, or re-working your programming around the annoying constraint of the toolkit you're programming in?


[ Parent ]

Time out on the select() (none / 0) (#423)
by hugues on Mon Nov 25, 2002 at 12:02:26 AM EST

Specify a tiny timeout in the select(), like 1ms. Poll your variables in a loop containing the select(). With current CPUs, your usage will be much less than 1%, and I can't think of may programs that can't afford the 1ms latency.

That's what I always do.

[ Parent ]

This is event-driven programming (4.00 / 1) (#397)
by Peaker on Thu Nov 21, 2002 at 02:43:21 PM EST

and is mentioned by the author of the article.

Further experimentation confirmed that the overhead of switching threads (especially in a UNIX environment where true threads don't really exist) was introducing the delays into the system.

This is quite funny, as I haven't seen any modern and/or popular *nix system that doesn't have true threads.

Ofcourse, your definition of 'true threads' may vary, but I define threads as contexts of running code that share resources, especially memory. If those threads are named (In Linux, up to 2.4) "Lightweight processes", so be it.

Ofcourse, you could also note that the measured overhead of context-switching between Linux threads ("Lightweight processes") is lower than the overhead of context-switching between threads in, say, Windows NT/2K/XP (Where you probably believe the threads, being named differently from processes, are somehow "true").

[ Parent ]

Ha ha what the hell are you talking about? (none / 0) (#408)
by sudog on Fri Nov 22, 2002 at 12:58:51 AM EST

You can't redefine "true" threads as something like what you just pulled out your ass. Here, be enlightened:

http://web.mit.edu/nathanw/www/usenix/freenix-sa/freenix-sa.html

Threads are sneaky tricks that need to be pre-emptive (supported by the kernel) to be threads. Else they're useless. You forgot to include that in your "definition."

[ Parent ]
Things I know about, unlike you (4.00 / 1) (#417)
by Peaker on Fri Nov 22, 2002 at 08:44:56 AM EST

Dude, Linux threads, a.k.a "lightweight processes" are implemented by the kernel.

Linux has been implementing lightweight processes ever since the clone(2) system call was introduced into Linux in 2.0 or even sooner.

[ Parent ]

What the hell has that got to do with anything? (none / 0) (#443)
by sudog on Wed Dec 11, 2002 at 02:41:45 AM EST

I never disputed that Linux implemented what it thinks are "threads" in kernel with the clone() call.

I am disputing your definition--which is wrong in some very important ways. I bet you didn't even read that link I gave you, did you? That's a definition of "threads" that's currently being implemented in NetBSD kernel-land that is radically different from anything you appear to have a grasp of.

This the best you can do?


[ Parent ]

Select() works on windows too... (none / 0) (#424)
by hugues on Mon Nov 25, 2002 at 02:54:10 AM EST

... perfectly. All version >= win95

It's part of wsock32.lib IIRC.

[ Parent ]

Article misses important point (2.00 / 3) (#323)
by swagr on Tue Nov 19, 2002 at 10:21:29 AM EST

I won't argue here for or against Object Oriented Design.
But the whole point of OOD is that, since you use software to model stuff (objects) in the universe, it makes sense that the software should be designed with that in mind.

Now when I'm pouring a cup of coffee, the coffee doesn't stop flowing in order to give someone down the hall the resources to open a door.
Why should I model my software that way?

are you sure ? (4.37 / 8) (#337)
by CH-BuG on Tue Nov 19, 2002 at 12:04:59 PM EST

As you're yourself part of the universe, things might just work like that (ie the coffee stops flowing while the door opens), it's just that the Great Scheduler hides it from you :-)

[ Parent ]
Don't be silly (1.00 / 1) (#352)
by swagr on Tue Nov 19, 2002 at 03:26:35 PM EST

We can never know what anything really "is". Just how it appears to us.

I don't think Existential Object Oriented Design makes much sense.

[ Parent ]

the Great Scheduler (4.00 / 1) (#402)
by werner on Thu Nov 21, 2002 at 06:34:31 PM EST

...i like that one. perhaps we can get it on the next british census form, like jedi knight.

[ Parent ]
YHBT (2.50 / 8) (#334)
by levsen on Tue Nov 19, 2002 at 11:36:57 AM EST

This post looks like a major troll to me. In any case, it strikes a chord with those programmers, that realize multithreading widens the gap between competent and not so competent ones and are finding themselves on the wrong side of that gap.
This comment is printed on 100% recycled electrons.
Tell ya what.. (1.00 / 1) (#406)
by sudog on Fri Nov 22, 2002 at 12:42:06 AM EST

When you can write an event-driven program instead of cheaping out by resorting to threads, come back and see if you have the heart to say that again.

:-)

[ Parent ]

In the meantime ... (5.00 / 1) (#414)
by levsen on Fri Nov 22, 2002 at 05:54:12 AM EST

Ok, we're taking the discussion to a really low level here, but so what, it's fun! So if you could in the meantime rebuild the Eiffel tower out of toothpicks, 'cause steel is for sissies?


This comment is printed on 100% recycled electrons.
[ Parent ]

Your analogy is false.. but funny. :) (none / 0) (#449)
by sudog on Sun Dec 29, 2002 at 01:55:05 PM EST

Anyway, it's false because it applies toothpicks (weak, unsafe for real buildings, etc) to the event queue, while applying steel to the threading model. :)

Many popular games are based around event queues. If you need robustness, clean handling, and real-time response that self-adjusts, event queues are the way to go. Threading just complicates matters. :)

Anyway, ttyl,


[ Parent ]

One misconception (3.11 / 9) (#338)
by eeee on Tue Nov 19, 2002 at 12:07:21 PM EST

I think, in writing this article, you are making one very big, unfounded assumption that I'm not sure I agree with.  Basically, the entire structure of your argument centers around the assumption that it's bad to have bugs in your program.

Now, at first glance it might seem obvious that bugs are something to be avoided.  We commonly talk to others of "debugging" a program, and pretend that in doing this we are not also (accidentally, or so we say anyway) adding bugs that weren't there before.  Even the term "bug" is derogatory, comparing defects to ugly, dirty insects that most people just want to squash.  EEEEUUUWWW, I don't even want to think about them.

However, I ask you -- doesn't your software development company or division have a QA or testing department, or at least one or two testers?  And isn't it the case that the job of these people is, in fact, to see out and FIND new bugs where there weren't any before?  Aren't these people, in effect, on a QUEST for bugs, wishing they could find some?  And don't you think that in some cases their idea of what a bug is is merely subjective -- like the old saying that beauty is in the eye of the beholder?  Perhaps some of those bugs are just wishful thinking, QA people looking for something to call a bug because they just couldn't find anything wrong with the program.

Not to mention another facet of this issue that's not often talked about -- the issue of continued employment.  If you wrote a totally perfect and flawless piece of software, well, where's the role for you after that?  Who needs you anymore, now that the software does everything the way it's supposed to and works perfectly?  You're probably exhausted from the superhuman effort it took to write a perfect program, and burned out on or bored with that particular language/programming platform/type of software to boot -- fat chance you'll have the brain wattage to start a whole new project.  You're probably getting older too, and you know how employers are about hiring young, underpayable talent straight out of school.  Wouldn't it be better to surreptitiously introduce a few bugs here and there, and wait for the eventual phone call?  After all, you're the only person who knows exactly how the software works -- as long as you don't "rebug" the program too fast or too obviously, you know they'll call you.  C'mon -- you've done this, haven't you?  Admit it -- we're among friends.

At the very least, you've definitely been in on brainstorming sessions with the objective of hashing out ideas for new functionality to propose to the client -- which, in effect, is saying to the client that the software they think works well could actually be better.  In other words, you've imagined the existence of a bug, and that bug ends up being the key to a whole new contract.  In fact, let's get right down to it -- imagine your company has fallen on hard times, but has recently been hired to write a brand-new spreadsheet program from scratch.  Right at the start, before any specing or coding has been done, there is nothing to the project but just one big bug: namely, the bug that the spreadsheet program doesn't exist yet!  The bug becomes, in essence, your company's raison d'etre!

In conclusion, I have to say that I really don't agree with your basic premise, that bugs should be considered harmful.  In fact, they are not only helpful, but completely indispensable.  I think any reasonable and open-minded programmer who considers my points carefully will agree.

Brilliant! Just brilliant! (none / 0) (#348)
by joto on Tue Nov 19, 2002 at 02:55:23 PM EST



[ Parent ]
Goto is Harmful? (3.00 / 2) (#340)
by Mr.Surly on Tue Nov 19, 2002 at 12:28:20 PM EST

I've gone back to doing C lately, after a long affair with Perl. Recently I had some rather hairy nested looping code. In Perl, this is no problem

(No, I can't format this better -- Why doesn't scoop support <pre>???)

OUTER:
while ( $this )
{
# Stuff

INNER:
while ( $that )
{
# More stuff
#
if ($continue_inner_loop) # perl's 'next' == c's 'continue' for you C programmers
{
continue INNER;
}

if ($continue_outer_loop)
{
continue OUTER;
}
}
}


Now, to accomplish this in C without a 'goto' would make the code a horrible nightmare. Anyone who understands Duff's device and how it works with the interals of C can appreciate that a goto is a good thing when used in this manner. While ( ... ) { #stuff While ( ...) { # Stuff } }

Break (3.00 / 2) (#342)
by DodgyGeezer on Tue Nov 19, 2002 at 01:13:03 PM EST

What's wrong with "break"ing from the inner loop to the outer?  If there's stuff in the outer loop after the inner loop that you don't want executed, I guess you'd have a repeated if with the outer continue there.

[ Parent ]
And you call yourself a perl programmer! (4.00 / 1) (#345)
by czth on Tue Nov 19, 2002 at 02:26:54 PM EST

  1. Type code into editor.
  2. Save file (name will be $file).
  3. perl -pi -e 's/\t/" " x $tablen/eg; s/^( {2,})/" " x length($1)/' $file (in vim feel free to use :%!perl -pe ...)
  4. PROFI... er... sorry, please disregard this line, it will be disciplined.
  5. Reopen file in editor, copy file, paste into comment box.
Of course a real perl programmer would do this all in one step.

czth

[ Parent ]

It's the font. (none / 0) (#376)
by Mr.Surly on Wed Nov 20, 2002 at 12:15:51 PM EST

<pre> gives access to a fixed-width font, which would be appropriate, and forces the browser to _not_ ignore the whitespace at the beginning of the line.

Even with the porportional font, that code block should look okay (since it does have indenting), but browsers screw it up with out <pre>

[ Parent ]
Once again, (2.75 / 8) (#343)
by trhurler on Tue Nov 19, 2002 at 01:45:37 PM EST

Someone writes a story that might as well be titled "you are incompetent, so don't do this."

I've written large threaded programs(in C no less!) that simply didn't have locking bugs. It isn't hard. The trick is to impose structure. Only create threads in certain places, and make sure you get the locking there correct for any data that changes hands. Only allow threads to exit or otherwise be destroyed in certain places, and again check that code carefully. Finally, only allow them to share data in a few tiny places, and check those carefully. If a thread needs to use shared data more than "very infrequently," then you've designed your program incorrectly.

Similarly, goto has legitimate uses in many languages despite its potential for abuse, but why go into those when the whole crowd here is a bunch of Java and Visual Basic wankers who think C++ is the epitome of languages which they one day aspire to learn and don't understand why those freaky looking people off in the back room treat them like idiots.

--
'God dammit, your posts make me hard.' --LilDebbie

Bad design is harmful (4.14 / 7) (#349)
by Maniac on Tue Nov 19, 2002 at 03:02:07 PM EST

There are plenty of good applications where threads (or another form of multiprocessing) is applicable. Other comments have mentioned a number of examples - let me add a few more:
  • real time data acquisition
  • process control
  • real time modelling and simulation
Developers have been delivering good systems for these applications using threads or other means for many years.

Let me touch briefly on the two "problems" you mention and get to the root cause of each and some other solutions.

Race Conditions

This problem actually represents a family of root causes but the primary one is non atomic modification of shared data. As an example, you insert an item into a list from one thread while traversing the list with another. You can have code like:

  1.  allocate item and set data values
  2.  item.next = prev.next
  3.  prev.next = &item
which works fine on most hardware but can fail on Alpha and other hardware where write ordering is not guaranteed. One solution is to add a write barrier between steps 2 and 3. Another - as you mentioned - is to take out a lock that prevents traversal while the list is being updated.

Another class of solution is to use a "better language". Using Ada for example, you can have protected objects where the run time library handles the details of atomic access and update for you. There are certainly other choices in this category.

Another class of solution is temporal separation of access. A method I have used is an "input phase", "processing and output phase" where the cooperating tasks (threads) synchronize to get their inputs, do the processing in parallel and output their data before the next synchronization. This works best for real time applications with well defined iteration rates.

Using the three solutions you suggested:
o multiple processes - this is not a fix for this problem. Shared memory between processes will still be a problem. You can use pipes or another message passing mechanism, but you could have done that with threads as well.
o Event based programming - does fix the problem. As others have mentioned it also may not be a good fit for your application.
o Cooperative threads - does fix the problem. Ditto.

Deadlock

Getting locks out of order is the root cause of deadlock. It also has a surprisingly simple solution... number the locks and always take them in order. I have even seen lock implementations where the lock order was enforced by the library (error return if out of order locking was detected).

Having said this - I maintain that bad design is harmful, not threads. This bad design can be based on hardware (e.g., non coherent caches) or software (e.g., uncontrolled access to shared data). Either case will cause the problems you mention, not threads. Threads are a tool to help the developer maintain concurrent execution of an application and can't fix a bad design.

Threads are not harmful: bad design is (4.55 / 9) (#350)
by hairyian on Tue Nov 19, 2002 at 03:12:02 PM EST

As far as I can see, the problems cited in this article aren't only restricted to programs with multiple threads but are simply more obvious in that environment.

They're a symptom of flawed design work. If you have resource sharing, then that needs to be part of the design of the program. Throwing in 'locks' willy nilly is not a well thought out or, as described above, even designed.

Really, this isn't a "Threads considered harmful" post but rather a "Resource sharing gone wrong considered harmful"... two distinct things.

One counter case, showing that threads in themselves are not harmful is the case of 'active' objects. Take an object which is a thread in itself. In true OO style, messages are sent to the active object, processed in whatever order it's programmed to do them in, and sends messages back (if needed).

I can't think of a situation where a deadlock can occur when using an active object. All the operations performed by the AO are atomic and other AOs don't need to be blocked waiting for a response (though they may choose to do so).

This is something akin to an "event"-driven system, but in practice is much more useful. In fact, programming using AO, at least to me, is much more natural than synchronous systems.

Ah... those Monitors are at it again, making resource sharing that bit easier.

Ian Woods

The problem with threads (4.00 / 2) (#357)
by Pseudonym on Tue Nov 19, 2002 at 08:28:32 PM EST

The event-based or coroutine models are perfect for the situation when you need a kind of concurrency, but there is so much cooperation that your program is effectively sequential anyway.

The multiple process model is perfect if you need concurrency, but the tasks don't really interfere.

Threads are for the cases in between: You need concurrency, you need low latency, you need SMP scalability, but the tasks need to rendezvous in ways that aren't easy to deal with in these other ways.

This, then, is the central problem with threads: The cases where you need them are precisely those cases which are hard to do regardless. It's not that threads are harmful, it's that their only use is to solve problems which are inherently tricky anyway. This is why multi-threading is correlated with design problems. Correlation is not, however, the same thing as causation.



sub f{($f)=@_;print"$f(q{$f});";}f(q{sub f{($f)=@_;print"$f(q{$f});";}f});
However (4.00 / 1) (#384)
by moshez on Thu Nov 21, 2002 at 12:32:30 AM EST

Many people use threads for the cases where the design is not inherently tricky. It serves to make a straightforward design tricky.

[T]he k5 troll HOWTO has been updated ... This update is dedicated to moshez, and other bitter anti-trolls.
[ Parent ]
Right tools for the right job (none / 0) (#385)
by DodgyGeezer on Thu Nov 21, 2002 at 12:49:43 AM EST

Of course, many people use threads where they are the most appropriate and natural approach, and perhaps the least costly to implement.

[ Parent ]
Title (2.16 / 6) (#362)
by ColPanic on Wed Nov 20, 2002 at 04:13:15 AM EST

I read the title as "Threats Considered Harmful"...

Harmful? (3.57 / 7) (#370)
by sto0 on Wed Nov 20, 2002 at 09:36:44 AM EST

As far as I can see, every singe potential problem presented in the article can be put down to bad programming, structurally speaking. No-one pretends that semaphore-style locks are not a difficult thing to maintain in large programs. If this is the case, then a eaiser logical structure should be used, such as monitors.

The treatment of locks as being the only way to implement concurrency is a bit silly.

Higher-order Concurrency (4.00 / 1) (#379)
by norge on Wed Nov 20, 2002 at 02:15:16 PM EST

I highly recommend that everyone out there who is interested in concurrent programming look up John Reppy's excellent Ph.D. thesis, ``Higher-order Concurrency''.  It may change the way you look at concurrent programming.

Benjamin


disappointing article (4.81 / 11) (#382)
by klash on Wed Nov 20, 2002 at 05:35:18 PM EST

From the intro, I was expecting an insightful look at why threads are not the best solution to most classes of problems they are often used for. I was hoping to see examples of instances where threads might seem to be the best solution, but then see the code rewritten without them in a way that keeps the same performance but is no longer prone to race conditions. I was hoping for a weighing of the tradeoffs between the threaded and non-threaded variations.

Instead I got a diatribe about a straw man who uses threads indiscriminately, without any plan for how data will be shared, and who randomly places locks to as band-aids each time he discovers a race condition. This is somehow supposed to convince me that threads are bad news and should always be avoided. As if the story of a drunk guy who cut off his thumb with a circular saw should convince carpenters to abandon power tools.

Now that I am supposedly convinced, three "alternatives" are offered, without any examples to illustrate how a problem that might be solved using threads could be better solved using these alternatives.

Sorry but I'm not convinced. When you're willing to discuss threads on their own terms, using believable examples with competent characters and real-world examples, I'm all ears.

Birthday Paradox misunderstood (3.80 / 5) (#388)
by k5testacc12 on Thu Nov 21, 2002 at 06:45:25 AM EST

This article seems to me (as humbly as I can say it) to be a simple case of "I think I know what I'm talking about", and is better served on slashdot than here.

Now that I've lost your attention, I'll just point out the birthday paradox error; others have sufficiently covered the core material.

The birthday paradox explains the probability that any two LOC would be executing at the same time, given equal probability for each LOC to execute. cf. My birthday having a 1/365 chance of being on any given day vs everyone else in the room's birthday having a 1/365 chance of being on any given day.

The chance that line 524 would be executing simultaneously in k of n threads would be 1 in (0.0001*n)^k, ie, just what one would suspect without any "paradoxes" thrown in.

That's assuming that spending 0.01% of time on a certain LOC in a certain thread is the same as a 0.01% probability that the given LOC will execute in any given thread; an assumption which is not true in general.

The use of "Birthday Paradox", and the use of the ACM "harmful" reference, is fluff designed to pique interest rather than being substantive material. This further detracts from an already poorly thought out article.

Birthday paradox understood well (5.00 / 1) (#396)
by Peaker on Thu Nov 21, 2002 at 02:23:13 PM EST

Analogies are not always completely accurate. The similiarity may be high to extremely high. You expect of his birthday paradox to be extremely similar to the situation at hand, while it is only highly similar.

The similiarity, and the main point of the birthday paradox, is that having X items to compare between can make the odds of a match between items far more than X times more likely, which may seem counter-intuitive.

The chance that line 524 would be executing simultaneously in k of n threads would be 1 in (0.0001*n)^k, ie, just what one would suspect without any "paradoxes" thrown in.

Lets choose k=2 threads, and n=3 threads. According to what you say, the chance that line 524 would be executing simultaneously in 2 of 3 threads would be 1 in 0.0003^2, which is 1 in 0.0000009. Not sure what you mean about "1 in .." here, but this doesn't seem correct. If "1 in .." was just a typo, then choosing a very large n lets you get higher-than-1 probabilities, which doesn't seem correct either.

Another, perhaps easier way to see that your calculation is incorrect, is that given exactly 10000 threads, it doesn't matter how many of them you choose, the result is still "1 of 1".

The real calculation here, like the one in the birthday paradox, involves substracting the probability of 'no match' occuring from 1, to get the probability of any match occuring:
The probability of at least one thread of X threads executing that line of code at some point in time: 1-(0.9999^X)
This means that, for example, with X=~1000 threads, the probability of at least one being in that LOC is about 10%.

For the final formula (That I will bother to find :), to have at least two threads in that LOC at the same time, is: 0.0001*(1 - (0.9999^(X-1))) + 0.9999*((0.0001*(1 - (0.9999^(X-2)))) + 0.9999*(...))
(The odds of the first thread to be in that LOC is 0.0001, and then the odds of at least one of the rest of the X-1 threads to be in that LOC is (1 - (0.9999^(X-1)). Then, if the first thread isn't in that LOC (0.9999), then maybe the second thread is in (0.0001 again) and so on.

This is not at all trivial and there might be a much simpler formula that expresses the same thing. However, the author of the parent of this comment is claiming it is simple, and accuses of lack of understanding, which he seems to have himself.

That's assuming that spending 0.01% of time on a certain LOC in a certain thread is the same as a 0.01% probability that the given LOC will execute in any given thread; an assumption which is not true in general.

This is nitpicking, and the relation between the time spent in a certain LOC and the odds of finding it in a given thread is pretty well connected.

The use of "Birthday Paradox", and the use of the ACM "harmful" reference, is fluff designed to pique interest rather than being substantive material. This further detracts from an already poorly thought out article.

The use of the birthday paradox was valid (as shown above, about 1000 concurrent threads bring the odds for a collision up far more than 1000 times).

The harmful reference was just an anecdote to add "fluff" indeed, which is completely valid and makes the article a more interesting read.

In summary, the main point you tried to cover lacked just in the same way you accuse the author of lacking, while the author was seriously and correctly attacking a serious problem in many programs these days.

[ Parent ]

State of the art? (none / 0) (#405)
by blp on Thu Nov 21, 2002 at 11:33:12 PM EST

Dijkstra solved the dining with philosophers problem in 1965. Why do you consider this "state of the art"?

I can no longer sit back and allow: Communist Infiltration, Communist Indoctrination, Communist Subversion and the International Communist Conspiracy to sap and inpurify all of our precious bodily fluids.

how about pseudothreads? (none / 0) (#416)
by Richard W M Jones on Fri Nov 22, 2002 at 08:03:02 AM EST

Pseudothreads are cooperative threads, built on top of an event-based Reactor model.

This way you get the safety of event-based programming, the efficiency of threads, but without the agony.

Rich.

Erlang! (5.00 / 1) (#420)
by kubalaa on Fri Nov 22, 2002 at 11:54:37 PM EST

No discussion of threads is complete without a mention of erlang. Basically, erlang's thread philosophy is as follows:

  • no shared data
  • no blocking
  • no guarantees that messages are recieved
  • threads should be really lightweight and really easy to use

Basically, it's like a cross between the safety of processes and the ease of use of threads.

Threads Considered Harmful | 451 comments (442 topical, 9 editorial, 0 hidden)
Display: Sort:

kuro5hin.org

[XML]
All trademarks and copyrights on this page are owned by their respective companies. The Rest 2000 - Present Kuro5hin.org Inc.
See our legalese page for copyright policies. Please also read our Privacy Policy.
Kuro5hin.org is powered by Free Software, including Apache, Perl, and Linux, The Scoop Engine that runs this site is freely available, under the terms of the GPL.
Need some help? Email help@kuro5hin.org.
My heart's the long stairs.

Powered by Scoop create account | help/FAQ | mission | links | search | IRC | YOU choose the stories!