On Android Chrome the web worker took 18 seconds while the non web worker took 11 seconds but was utterly unresponsive. Having a responsive ui using the web worker with later results than a non responsive ui with faster results is a trade off I'll take any day.
That requires decomposing problems in more unnatural ways and introducing greater overhead.
Using a trivial sort example, you're suggesting something like:
sort (Array):
i := 0;
while(not-sorted):
sort-step
i := i + 1
if i % 100 = 0:
yield
This requires tuning, and all sorts of fun things. And a redesign of your algorithms.
With something like webworkers, you don't have to alter your algorithms in any fundamental way. They're left alone and the change is how they're plugged into each other. You allow the scheduler to handle yielding and other details.
Starting a thread is usually lazy inefficient programming. IMHO programmers shouldn't be allowed to try using threads until they're actually competent working in a single thread.
Ok. So they're supposed to manually create all the potential benefits of multithreading with manual yields and coroutines and the like?
I have an application, it does the following things:
- Presents an interface to the user
- Loads data files
- Loads a test description file
- Processes the data files per what's in the test description
- Displays all this information
- Provides custom filtering options for data analysis
You propose that I should do all of this in a single thread? The test processing can take a minute in some cases (couple hundred megs of data, couple hundred test cases to consider, rare but happens). The user shouldn't be able to use the other filtering tools while this is going on?
In some cases, loading the data files can take several minutes (again, hundreds of megs sometimes) if done over the network. I can't stop them from doing that. Should the UI lockup during this time preventing them from doing other things? (Admittedly, at that point there's only two other things they might do, change a couple options and specify which test description file to use).
It's a simple app, but it definitely benefits from multi-threading. I don't need to come up with some convoluted method of interleaving different tasks that have different run-time considerations. The scheduler can and should take care of that for me.
EDIT: I also assume you wouldn't want anyone to use languages like go or erlang where multi-threading (with green threads) is the expected use case.
Of course it's far better to start several threads, throw all the instructions at the task scheduler, have it execute them in a random order, deal with synchronization and concurrency issues. Of course that's better...
You have a program that needs to run tasks A,B,C (can be in parallel) followed by D (must be sequential, after the others).
You can optionally put everything in a serial process:
ABCD
But that's only "optimal" if you have a single core, and no asynchronous tasks (like a delay in B for accessing a file over the network, where it doesn't touch the CPU for 10 seconds).
Or you can design it like:
(A|B|C)D % where | indicates independence, parallelism
Now, if we have a language like erlang or go, you can do something like (erlang is rusty):
main() ->
Pids = lists:map(fun (F) -> spawn(F, [self()]) end, [fun a/1, fun b/1, fun c/1]),
lists:map(fun (Pid) -> receive
Pid -> ok
end
end, Pids),
d().
There, done. The scheduler, now, will handle the interleaving of each of those independent processes across the available cores.
If you want to do that manually, without preemptive multitasking, have fun tuning it correctly. Do you have each parallel task look like this:
TaskA:
while(work-to-be-done)
do-work
yield
And just hope for the best? Is the context switching really going to be worth it? You're running a yield on each iteration of the loop now. That's clearly wasteful as you lose out on things like branch prediction, cache speed versus RAM speed, for running over this a number of times.
So now you have to tune each one of these if you really want the optimal results. And you have to know precisely what machine your target system is running. Yeah, in the embedded world, we've got that, and we pretty much do exactly what you suggest. Because we control the full stack. We know exactly what software runs on a CPU. We know exactly what the CPU speed will be. Memory access times. We can come up with near optimal, low-level coroutine style programs like this.
But most programs don't run in such a known, quantified, and understood environment.