Выбрать главу

One of the biggest difficulties with threads occurs because more than one thread might be sharing a resource—such as the memory in an object—and you must make sure that multiple threads don’t try to read and change that resource at the same time. This requires judicious use of synchronization tools, which must be thoroughly understood because they can quietly introduce deadlock situations.

In addition, there’s a certain art to the application of threads. C++ is designed to allow you to create as many objects as you need to solve your problem—at least in theory. (Creating millions of objects for an engineering finite-element analysis, for example, might not be practical.) However, there is usually an upper bound to the number of threads you’ll want to create, because at some number, threads may become balky. This critical point can be difficult to detect and will often depend on the OS and thread library; it could be fewer than a hundred or in the thousands. As you often create only a handful of threads to solve a problem, this is typically not much of a limit; but in a more general design it becomes a constraint.

Regardless of how simple threading can seem using a particular language or library, consider it a black art. There’s always something you haven’t considered that can bite you when you least expect it. (For example, note that because the dining philosophers problem can be adjusted so that deadlock rarely happens, you can get the impression that everything is OK.) An appropriate quote comes from Guido Van Rossum, creator of the Python programming language:

In any project that is multi-threaded, most bugs will come from threading issues. This is regardless of programming language—it’s a deep, as yet un-understood property of threads.

For more advanced discussions of threading, see Concurrent Programming in Java, 2nd Edition, by Doug Lea, Addison-Wesley, 2000.

Exercises

Solutions to selected exercises can be found in the electronic document The Thinking in C++ Volume 2 Annotated Solution Guide, available for a small fee from www.BruceEckel.com.

             1.             Inherit a class from Runnable and override the run( ) function. Inside run( ), print a message, and then call sleep( ). Repeat this three times, and then return from run( ). Put a start-up message in the constructor and a shut-down message when the task terminates. Make several thread objects of this type, and run them to see what happens.

             2.             Modify BasicThreads.cpp to make LiftOff threads start other LiftOff threads.

             3.             Modify ResponsiveUI.cpp to eliminate any possible race conditions. (Assume bool operations are not atomic.)

             4.             In Incrementer.cpp, modify the Count class to use a single int instead of an array of int. Explain the resulting behavior.

             5.             In EvenChecker.h, correct the potential problem in the Generator class. (Assume bool operations are not atomic.)

             6.             Modify EvenGenerator.cpp to use interrupt( ) instead of quit flags.

             7.             In MutexEvenGenerator.cpp, change the code in MutexEvenGenerator::nextValue( ) so that the return expression precedes the release( ) statement and explain what happens.

             8.             Modify ResponsiveUI.cpp to use interrupt( ) instead of the quitFlag approach.

             9.             Look up the Singleton documentation in the ZThreads library. Modify OrnamentalGarden.cpp so that the Display object is controlled by a Singleton to prevent more than one Display from being accidentally created.

        10.             In OrnamentalGarden.cpp, change the Count::increment( ) function so that it does a direct increment of count. Now remove the guard and see if that causes a failure. Is this safe and reliable?

         11.             Modify OrnamentalGarden.cpp so that it uses interrupt( ) instead of the pause( ) mechanism. Make sure that your solution doesn’t prematurely destroy objects.

         12.             Generate assembly code from C++ examples to discover which operations (bool assignment, int increment, and so on) your particular compiler performs atomically.

         13.             Modify WaxOMatic.cpp by adding more instances of the Process class so that it applies and polishes three coats of wax instead of just one.

         14.             Create two Runnable subclasses, one with a run( ) that starts up and then calls wait( ). The other class’s run( ) should capture the reference of the first Runnable object. Its run( ) should call signal( ) for the first thread after some number of seconds have passed so that first thread can print a message.

         15.             Create an example of a "busy wait." One thread sleeps for awhile and then sets a flag to true. The second thread watches that flag inside a while loop (this is the "busy wait") and, when the flag becomes true, sets it back to false and reports the change to the console. Note how much wasted time the program spends inside the "busy wait," and create a second version of the program that uses wait( ) instead of the "busy wait." Extra: run a profiler to show the time used by the CPU in each case.

         16.             Modify ToastOMaticMarkII.cpp to create peanut-butter and jelly on toast sandwiches using two separate assembly lines and an output TQueue for the finished sandwiches. Use a Reporter object as in CarBuilder.cpp to display the results.

         17.             Rewrite C07:BankTeller.cpp to use real threading instead of simulated threading.

         18.             Modify CarBuilder.cpp to give identifiers to the robots, and add more instances of the different kinds of robots. Note whether all robots get utilized.

         19.             Modify CarBuilder.cpp to add another stage to the car-building process, whereby you add the exhaust system, body, and fenders. As with the first stage, assume these processes can be performed simultaneously by robots.

        20.             Modify CarBuilder.cpp so that Car has synchronized access to all the bool variables. Because Mutexes cannot be copied, this will require significant changes throughout the program.