logo资料库

C#关于线程的讲解和例子.pdf

第1页 / 共42页
第2页 / 共42页
第3页 / 共42页
第4页 / 共42页
第5页 / 共42页
第6页 / 共42页
第7页 / 共42页
第8页 / 共42页
资料共42页,剩余部分请下载后查看
csphtp1.book Page 590 Wednesday, November 21, 2001 11:59 AM 14 Multithreading Objectives To understand the notion of multithreading. To appreciate how multithreading can improve program performance. To understand how to create, manage and destroy threads. To understand the life cycle of a thread. To understand thread synchronization. To understand thread priorities and scheduling. To understand the role of a ThreadPool in efficient multithreading. The spider’s touch, how exquisitely fine! Feels at each thread, and lives along the line. Alexander Pope A person with one watch knows what time it is; a person with two watches is never sure. Proverb Learn to labor and to wait. Henry Wadsworth Longfellow The most general definition of beauty…Multeity in Unity. Samuel Taylor Coleridge
csphtp1.book Page 591 Wednesday, November 21, 2001 11:59 AM Chapter 14 Multithreading 591 Outline 14.1 14.2 14.3 14.4 14.5 14.6 14.7 Introduction Thread States: Life Cycle of a Thread Thread Priorities and Thread Scheduling Thread Synchronization and Class Monitor Producer/Consumer Relationship without Thread Synchronization Producer/Consumer Relationship with Thread Synchronization Producer/Consumer Relationship: Circular Buffer Summary Terminology Self-Review Exercises Answers to Self-Review Exercises Exercises 14.1 Introduction It would be nice if we could perform one action at a time and perform it well, but that is usually difficult to do. The human body performs a great variety of operations in parallel— or, as we will say throughout this chapter, concurrently. Respiration, blood circulation and digestion, for example, can occur concurrently. All the senses—sight, touch, smell, taste and hearing—can occur at once. Computers, too, perform operations concurrently. It is common for desktop personal computers to be compiling a program, sending a file to a printer and receiving electronic mail messages over a network concurrently. Ironically, most programming languages do not enable programmers to specify con- current activities. Rather, programming languages generally provide only a simple set of control structures that enable programmers to perform one action at a time, proceeding to the next action after the previous one has finished. Historically, the type of concurrency that computers perform today generally has been implemented as operating system “primitives” available only to highly experienced “systems programmers.” The Ada programming language, developed by the United States Department of Defense, made concurrency primitives widely available to defense contractors building military command-and-control systems. However, Ada has not been widely used in univer- sities and commercial industry. The .NET Framework Class Library makes concurrency primitives available to the applications programmer. The programmer specifies that applications contain “threads of execution,” each thread designating a portion of a program that may execute concurrently with other threads—this capability is called multithreading. Multithreading is available to all .NET programming languages, including C#, Visual Basic and Visual C++. Software Engineering Observation 14.1 The .NET Framework Class Library includes multithreading capabilities in namespace System.Threading. This encourages the use of multithreading among a larger part of the applications-programming community. 14.1 We discuss many applications of concurrent programming. When programs download large files, such as audio clips or video clips from the World Wide Web, users do not want to wait until an entire clip downloads before starting the playback. To solve this problem, we can put multiple threads to work—one thread downloads a clip, and another plays the
csphtp1.book Page 592 Wednesday, November 21, 2001 11:59 AM 592 Multithreading Chapter 14 clip. These activities, or tasks, then may proceed concurrently. To avoid choppy playback, we synchronize the threads so that the player thread does not begin until there is a sufficient amount of the clip in memory to keep the player thread busy. Another example of multithreading is C#’s automatic garbage collection. C and C++ place with the programmer the responsibility of reclaiming dynamically allocated memory. C# provides a garbage-collector thread that reclaims dynamically allocated memory that is no longer needed. Performance Tip 14.1 One of the reasons for the popularity of C and C++ over the years was that their memory- management techniques were more efficient than those of languages that used garbage col- lectors. In fact, memory management in C# often is faster than in C or C++.1 14.1 Good Programming Practice 14.1 Set an object reference to null when the program no longer needs that object. This enables the garbage collector to determine at the earliest possible moment that the object can be gar- bage collected. If such an object has other references to it, that object cannot be collected. 14.1 Writing multithreaded programs can be tricky. Although the human mind can perform functions concurrently, people find it difficult to jump between parallel “trains of thought.” To see why multithreading can be difficult to program and understand, try the following experiment: Open three books to page 1 and try reading the books concurrently. Read a few words from the first book, then read a few words from the second book, then read a few words from the third book, then loop back and read the next few words from the first book, etc. After this experiment, you will appreciate the challenges of multithreading—switching between books, reading briefly, remembering your place in each book, moving the book you are reading closer so you can see it, pushing books you are not reading aside—and amidst all this chaos, trying to comprehend the content of the books! Performance Tip 14.2 A problem with single-threaded applications is that lengthy activities must complete before other activities can begin. In a multithreaded application, threads can share a processor (or set of processors), so that multiple tasks are performed in parallel. 14.2 14.2 Thread States: Life Cycle of a Thread At any time, a thread is said to be in one of several thread states (illustrated in Fig. 14.12). This section discusses these states and the transitions between states. Two classes critical for multithreaded applications are Thread and Monitor (System.Threading namespace). This section also discusses several methods of classes Thread and Moni- tor that cause state transitions. 1. E. Schanzer, “Performance Considerations for Run-Time Technologies in the .NET Framework,” August 2001 . 2. As this book went to publication, Microsoft changed the names of the Started and Blocked thread states to Running and WaitSleepJoin, respectively.
csphtp1.book Page 593 Wednesday, November 21, 2001 11:59 AM Chapter 14 Multithreading 593 Unstarted Start Pulse PulseAll Interrupt sleep interval expires Started quantum expiration dispatch (assign a processor) Running I/O completion Wait Sleep, Join Suspend complete Issue I/O request WaitSleepJoin Suspended Stopped Blocked Resume Fig. 14.1 Fig. 14.1 Fig. 14.1 Fig. 14.1 Thread life cycle. A new thread begins its lifecyle in the Unstarted state. The thread remains in the Unstarted state until the program calls Thread method Start, which places the thread in the Started state (sometimes called the Ready or Runnable state) and immediately returns control to the calling thread. Then the thread that invoked Start, the newly Started thread and any other threads in the program execute concurrently. The highest priority Started thread enters the Running state (i.e., begins executing) when the operating system assigns a processor to the thread (Section 14.3 discusses thread priorities). When a Started thread receives a processor for the first time and becomes a Run- ning thread, the thread executes its ThreadStart delegate, which specifies the actions the thread will perform during its lifecyle. When a program creates a new Thread, the pro- gram specifies the Thread’s ThreadStart delegate as the argument to the Thread constructor. The ThreadStart delegate must be a method that returns void and takes no arguments. A Running thread enters the Stopped (or Dead) state when its ThreadStart dele- gate terminates. Note that a program can force a thread into the Stopped state by calling Thread method Abort on the appropriate Thread object. Method Abort throws a ThreadAbortException in the thread, normally causing the thread to terminate. When a thread is in the Stopped state and there are no references to the thread object, the garbage collector can remove the thread object from memory.
csphtp1.book Page 594 Wednesday, November 21, 2001 11:59 AM 594 Multithreading Chapter 14 A thread enters the Blocked state when the thread issues an input/output request. The operating system blocks the thread from executing until the operating system can complete the I/O for which the thread is waiting. At that point, the thread returns to the Started state, so it can resume execution. A Blocked thread cannot use a processor even if one is available. There are three ways in which a Running thread enters the WaitSleepJoin state. If a thread encounters code that it cannot execute yet (normally because a condition is not sat- isfied), the thread can call Monitor method Wait to enter the WaitSleepJoin state. Once in this state, a thread returns to the Started state when another thread invokes Monitor method Pulse or PulseAll. Method Pulse moves the next waiting thread back to the Started state. Method PulseAll moves all waiting threads back to the Started state. A Running thread can call Thread method Sleep to enter the WaitSleepJoin state for a period of milliseconds specified as the argument to Sleep. A sleeping thread returns to the Started state when its designated sleep time expires. Sleeping threads cannot use a processor, even if one is available. Any thread that enters the WaitSleepJoin state by calling Monitor method Wait or by calling Thread method Sleep also leaves the WaitSleepJoin state and returns to the Started state if the sleeping or waiting Thread’s Interrupt method is called by another thread in the program. If a thread cannot continue executing (we will call this the dependent thread) unless another thread terminates, the dependent thread calls the other thread’s Join method to “join” the two threads. When two threads are “joined,” the dependent thread leaves the WaitSleepJoin state when the other thread finishes execution (enters the Stopped state). If a Running Thread’s Suspend method is called, the Running thread enters the Sus- pended state. A Suspended thread returns to the Started state when another thread in the program invokes the Suspended thread’s Resume method. 14.3 Thread Priorities and Thread Scheduling Every thread has a priority in the range between ThreadPriority.Lowest to ThreadPriority.Highest. These two values come from the ThreadPriority enumeration (namespace System.Threading). The enumeration consists of the values Lowest, BelowNormal, Normal, AboveNormal and Highest. By default, each thread has priority Normal. The Windows operating system supports a concept, called timeslicing, that enables threads of equal priority to share a processor. Without timeslicing, each thread in a set of equal-priority threads runs to completion (unless the thread leaves the Running state and enters the WaitSleepJoin, Suspended or Blocked state) before the thread’s peers get a chance to execute. With timeslicing, each thread receives a brief burst of processor time, called a quantum, during which the thread can execute. At the completion of the quantum, even if the thread has not finished executing, the processor is taken away from that thread and given to the next thread of equal priority, if one is available. The job of the thread scheduler is to keep the highest-priority thread running at all times and, if there is more than one highest-priority thread, to ensure that all such threads execute for a quantum in round-robin fashion. Figure 14.2 illustrates the multilevel priority queue for threads. In Fig. 14.2, assuming a single-processor computer, threads A and B each execute for a quantum in round-robin fashion until both threads complete execution. This means that A gets a quantum of time to run. Then B gets a quantum. Then A gets another quantum. Then
csphtp1.book Page 595 Wednesday, November 21, 2001 11:59 AM Chapter 14 Multithreading 595 B gets another quantum. This continues until one thread completes. The processor then devotes all its power to the thread that remains (unless another thread of that priority is Started). Next, thread C runs to completion. Threads D, E and F each execute for a quantum in round-robin fashion until they all complete execution. This process continues until all threads run to completion. Note that, depending on the operating system, new higher-priority threads could postpone—possibly indefinitely—the execution of lower-priority threads. Such indefinite postponement often is referred to more colorfully as starvation. A thread’s priority can be adjusted with the Priority property, which accepts values from the ThreadPriority enumeration. If the argument is not one of the valid thread-priority constants, an ArgumentException occurs. A thread executes until it dies, becomes Blocked for input/output (or some other reason), calls Sleep, calls Monitor method Wait or Join, is preempted by a thread of higher priority or has its quantum expire. A thread with a higher priority than the Running thread can become Started (and hence preempt the Running thread) if a sleeping thread wakes up, if I/O completes for a thread that Blocked for that I/O, if either Pulse or PulseAll is called on an object on which Wait was called, or if a thread to which the high-priority thread was Joined completes. Figure 14.3 demonstrates basic threading techniques, including the construction of a Thread object and using the Thread class’s static method Sleep. The program cre- ates three threads of execution, each with the default priority Normal. Each thread dis- plays a message indicating that it is going to sleep for a random interval of from 0 to 5000 milliseconds, then goes to sleep. When each thread awakens, the thread displays its name, indicates that it is done sleeping, terminates and enters the Stopped state. You will see that method Main (i.e., the Main thread of execution) terminates before the application termi- nates. The program consists of two classes—ThreadTester (lines 8–41), which creates the three threads, and MessagePrinter (lines 44–73), which defines a Print method containing the actions each thread will perform. Objects of class MessagePrinter (lines 44–73) control the lifecycle of each of the three threads class ThreadTester’s Main method creates. Class MessagePrinter consists of instance variable sleepTime (line 46), static variable random (line 47), a constructor (lines 50–54) and a Print method (lines 57–71). Variable sleepTime stores a random integer value chosen when a new MessagePrinter object’s constructor is called. Each thread controlled by a MessagePrinter object sleeps for the amount of time specified by the corresponding MessagePrinter object’s sleepTime The MessagePrinter constructor (lines 50–54) initializes sleepTime to a random integer from 0 up to, but not including, 5001 (i.e., from 0 to 5000). Method Print begins by obtaining a reference to the currently executing thread (line 60) via class Thread’s static property CurrentThread. The currently executing thread is the one that invokes method Print. Next, lines 63–64 display a message indi- cating the name of the currently executing thread and stating that the thread is going to sleep for a certain number of milliseconds. Note that line 64 uses the currently executing thread’s Name property to obtain the thread’s name (set in method Main when each thread is cre- ated). Line 66 invokes static Thread method Sleep to place the thread into the Wait- SleepJoin state. At this point, the thread loses the processor and the system allows another thread to execute. When the thread awakens, it reenters the Started state again until the system assigns a processor to the thread. When the MessagePrinter object enters the
csphtp1.book Page 596 Wednesday, November 21, 2001 11:59 AM 596 Multithreading Chapter 14 Running state again, line 69 outputs the thread’s name in a message that indicates the thread is done sleeping, and method Print terminates. Class ThreadTester’s Main method (lines 10–39) creates three objects of class MessagePrinter, at lines 14, 19 and 24, respectively. Lines 15–16, 20–21 and 25–26 create and initialize three Thread objects. Lines 17, 22 and 27 set each Thread’s Name property, which we use for output purposes. Note that each Thread’s constructor receives a ThreadStart delegate as an argument. Remember that a ThreadStart delegate specifies the actions a thread performs during its lifecyle. Line 16 specifies that the delegate for thread1 will be method Print of the object to which printer1 refers. When thread1 enters the Running state for the first time, thread1 will invoke printer1’s Print method to perform the tasks specified in method Print’s body. Thus, thread1 will print its name, display the amount of time for which it will go to sleep, sleep for that amount of time, wake up and display a message indicating that the thread is done sleeping. At that point method Print will terminate. A thread completes its task when the method specified by a Thread’s ThreadStart delegate terminates, placing the thread in the Stopped state. When thread2 and thread3 enter the Running state for the first time, they invoke the Print methods of printer2 and printer3, respectively. Threads thread2 and thread3 perform the same tasks as thread1 by executing the Print methods of the objects to which printer2 and printer3 refer (each of which has its own randomly chosen sleep time). Priority Highest Priority AboveNormal Priority Normal Priority BelowNormal Priority Lowest Ready threads B E F A C D G Fig. 14.2 Fig. 14.2 Fig. 14.2 Fig. 14.2 Thread-priority scheduling. // Fig. 14.3: ThreadTester.cs // Multiple threads printing at different intervals. 1 2 3 Fig. 14.3 Fig. 14.3 Fig. 14.3 Fig. 14.3 Threads sleeping and printing. (Part 1 of 3.)
csphtp1.book Page 597 Wednesday, November 21, 2001 11:59 AM Chapter 14 Multithreading 597 using System; using System.Threading; // class ThreadTester demonstrates basic threading concepts class ThreadTester { static void Main( string[] args ) { // Create and name each thread. Use MessagePrinter's // Print method as argument to ThreadStart delegate. MessagePrinter printer1 = new MessagePrinter(); Thread thread1 = new Thread ( new ThreadStart( printer1.Print ) ); thread1.Name = "thread1"; MessagePrinter printer2 = new MessagePrinter(); Thread thread2 = new Thread ( new ThreadStart( printer2.Print ) ); thread2.Name = "thread2"; MessagePrinter printer3 = new MessagePrinter(); Thread thread3 = new Thread ( new ThreadStart( printer3.Print ) ); thread3.Name = "thread3"; Console.WriteLine( "Starting threads" ); // call each thread's Start method to place each // thread in Started state thread1.Start(); thread2.Start(); thread3.Start(); Console.WriteLine( "Threads started\n" ); } // end method Main } // end class ThreadTester // Print method of this class used to control threads class MessagePrinter { private int sleepTime; private static Random random = new Random(); // constructor to initialize a MessagePrinter object public MessagePrinter() { // pick random sleep time between 0 and 5 seconds sleepTime = random.Next( 5001 ); } 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 Fig. 14.3 Fig. 14.3 Fig. 14.3 Fig. 14.3 Threads sleeping and printing. (Part 2 of 3.)
分享到:
收藏