Which programs use multithreading




















Multithreaded programs seem harder or more complex to write because two or more concurrent threads working incorrectly make a much bigger mess a whole lot faster than a single thread can.

The "multithreading is hard" fallacy is propagated by programmers who, out of their depth in a single-threaded world, jump feet first into multithreading — and drown. Rather than re-examine their development practices or preconceived notions, they stubbornly try to "fix" things, and use the "multithreading is hard" excuse to justify their unreliable programs and missed delivery dates. Note that I'm talking here about the majority of programs that use multithreading.

There are difficult multithreading scenarios, just as there are difficult scenarios in the single-threaded world. But those are relatively rare. For the majority of what most programmers do, the problems just aren't that complicated. We move data around, transform it, perhaps do some calculations from time to time, and finally store the results in a database or display them on the screen.

Upgrading a typical single-threaded program so that it uses multiple threads isn't or shouldn't be very difficult. It becomes difficult for two reasons:. Most of what they were taught in introductory multithreading materials is technically correct but completely irrelevant to the problems at hand. The most important concepts in programming are universal; they apply equally to single-threaded and multithreaded programs. Programmers who drown in a sea of threads haven't learned the important lessons from writing single-threaded programs.

I know this because they make the same fundamental mistakes in their multithreaded programs as they do in their single-threaded programs.

Probably the most important lesson to be learned from the past 60 years of software development is that global mutable state is bad. Really bad. Programs that depend on global mutable state are harder to reason about and generally less reliable, because there are too many possible ways for the state to change.

There is a huge amount of research to back up that generalization, and countless design patterns whose primary purpose is to implement some type of data hiding. The best thing you can do to make your programs easier to reason about is to eliminate as much global mutable state as possible.

In a single-threaded sequential program, the likelihood of data being mangled is proportional to the number of components that can modify that data. It's usually not possible to completely eliminate global state, but we developers have very effective tools for strictly controlling which parts of a program can modify it. In addition, we've learned to create restrictive API layers around primitive data structures so that we also control how those data structures are changed.

The problems of global mutable state became more apparent in the late '80s and early '90s with the widespread use of event-oriented programming.

Programs no longer start at the beginning and follow a single predictable path to conclusion. Instead, the program has an initial state and events occur at unpredictable times in an unpredictable order. The code is still single-threaded, but it's asynchronous.

The likelihood of data being mangled increases because the order in which events can occur is a factor. It's not uncommon to find that if event A occurs before event B, then everything's fine. But if A follows B, especially if event C occurs in between, then the data is mangled beyond recognition. Adding concurrent threads complicates the problem even further because multiple methods can manipulate the global state at the same time.

It becomes impossible to reason about how the global state is changing. Not only is the order of events unpredictable, but multiple threads of execution can be updating the state at the same time. At least in the asynchronous case you can guarantee that one event will complete its processing before any other event can start.

In short, it is possible to say with certainty what the global state will be at the end of an event's processing. With multiple threads it's impossible in the general case to say which events will execute concurrently, and it's therefore impossible to say what the global state is at any given point in time.

A multithreaded program with extensive global mutable state is one of the best demonstrations of the Heisenberg uncertainty principle I know of. It's impossible to examine the state without changing the program's behavior.

When I launch into my prepared rant about global mutable state a somewhat expanded version of the last few paragraphs , programmers roll their eyes and tell me that they already know that. The programs are filled with global mutable state, and the programmers wonder why their programs don't work.

Not surprisingly, the most important part of creating a multithreaded program is design: figuring out what the program has to do, designing independent modules to perform those functions, clearly identifying what data each module needs, and defining the communications paths between modules.

Some things take priority. The key to success is, as with a single-threaded program, limiting interactions between the modules. If you eliminate shared mutable state, then data sharing problems are impossible. You might think that you can't afford the time to design your application so that it doesn't use global state.

In my opinion you can't afford not to. Trying to manage global mutable state kills more multithreaded programs than anything else. The more you have to manage, the more likely it is that your program will crash and burn. Most real world programs require some shared state that can be changed, and that's where programmers most often get into trouble. Seeing the need for sharing state, programmers often reach into their multithreading toolbox and pull out the only tool they have: the all-purpose lock critical section, mutex, or whatever it's called in their particular language.

An entire application will not block, or otherwise wait, pending the completion of another request. Improved server responsiveness.

Large or complex requests or slow clients don't block other requests for service. The overall throughput of the server is much greater. Minimized system resource usage. Threads impose minimal impact on system resources. This may be true of other tools, as well. Difficulty of writing code Multithreaded and multicontexted applications are not easy to write.

Only experienced programmers should undertake coding for these types of applications. Difficulty of debugging It is much harder to replicate an error in a multithreaded or multicontexted application than it is to do so in a single-threaded, single-contexted application.

As a result, it is more difficult, in the former case, to identify and verify root causes when errors occur. Difficulty of managing concurrency The task of managing concurrency among threads is difficult and has the potential to introduce new problems into an application. Difficulty of testing Testing a multithreaded application is more difficult than testing a single-threaded application because defects are often timing-related and more difficult to reproduce.



0コメント

  • 1000 / 1000