is unblocked so that it is once again eligible to run. Ready threads are waiting for a processor. Also, when a running thread is preempted, for example, if it is timesliced (because it has run too long), the thread immediately becomes ready.

A thread becomes running when it was ready and a processor selects the thread for execution. Usually this means that some other thread has blocked, or has been preempted by a timeslice—the blocking (or preempted) thread saves its context and restores the context of the next ready thread to replace itself. On a multiprocessor, however, a previously unused processor may execute a readied thread without any other thread blocking.

A thread becomes blocked when it attempts to lock a mutex that is currently locked, when it waits on a condition variable, when it calls sigwait for a signal that is not currently pending, or when it attempts an I/O operation that cannot be immediately completed. A thread may also become blocked for other system operations, such as a page fault.

When a thread is unblocked after a wait for some event, it is made ready again. It may execute immediately, for example, if a processor is available. In lifecycle.c, the main thread blocks at line 23, in pthread_join, to wait for the thread it created to run. If the thread had not already run at this point, it would move from ready to running when main becomes blocked. As the thread runs to completion and returns, the main thread will be unblocked—returning to the ready state. When processor resources are available, either immediately or after the thread becomes terminated, main will again become running, and complete.

2.2.4 Termination

A thread usually terminates by returning from its start function (the one you pass to the pthread_create function). The thread shown in lifecycle.c terminates by returning the value NULL, for example. Threads that call pthread_exit or that are canceled using pthread_cancel also terminate after calling each cleanup handler that the thread registered by calling pthread_cleanup_push and that hasn't yet been removed by calling pthread_cleanup_pop. Cleanup handlers are discussed in Section 5.3.3.

Threads may have private 'thread-specific data' values (thread-specific data is discussed in Section 5.4). If the thread has any non-NULL thread-specific data values, the associated destructor functions for those keys (if any) are called.

If the thread was already detached it moves immediately to the next section, recycling. Otherwise, the thread becomes terminated. It will remain available for another thread to join with it using pthread_join. This is analogous to a UNIX process that's terminated but hasn't yet been 'reaped' by a wait operation. Sometimes it is called a 'zombie' because it still exists even though it is 'dead.' A zombie may retain most or all of the system resources that it used when running, so it is not a good idea to leave threads in this state for longer than necessary. Whenever you create a thread with which you won't need to join, you should use the detachstate attribute to create it 'detached' (see Section 5.2.3).

At a minimum, a terminated thread retains the identification (pthread_t value) and the void* return value that was returned from the thread's start function or specified in a call to pthread_exit. The only external difference between a thread that terminated 'normally' by returning or calling pthread_exit, and one that terminated through cancellation, is that a cancelled thread's return value is always PTHREAD_CANCELLED. (This is why 'cancelled' is not considered a distinct thread state.)

If any other thread is waiting to join with the terminating thread, that thread is awakened. It will return from its call to pthread_join with the appropriate return value. Once pthread_join has extracted the return value, the terminated thread is detached by pthread_join, and may be recycled before the call to pthread_join returns. This means that, among other things, the returned value should never be a stack address associated with the terminated thread's stack— the value at that address could be overwritten by the time the caller could use it. In lifecycle. c, the main thread will return from the pthread_join call at line 23 with the value NULL.

pthread_join is a convenience, not a rule.

Even when you need a return value from a thread that you create, it is often at least as simple to create the thread detached and devise your own customized return mechanism as it is to use pthread_join. For example, if you pass information to a worker thread in some form of structure that another thread can find later, you might have the worker thread simply place the result in that same

structure and broadcast a condition variable when done. The Pthreads context for the thread, including the thread identifier, can then be recycled immediately when the thread is done, and you still have the part you really need, the return value, where you can find it easily at any time.

If pthread_join does exactly what you want, then by all means use it. But remember that it is nothing more than a convenience for the simplest and most limited model of communicating a thread's results. If it does not do exactly what you need, build your own return mechanism instead of warping your design to fit the limitations ofpthread_join.

2.2.5 Recycling

If the thread was created with the detachstate attribute set to PTHREAD_ CREATE_DETACHED (see Section 5.2.3), or if the thread or some other thread has already called pthread_detach for the thread's identifier, then the thread is immediately recycled when it becomes terminated.

If the thread has not been detached when it terminates, it remains in the terminated state until the thread's pthread_t identifier is passed to pthread_detach or pthread_join. When either function returns, the thread cannot be accessed again. In lifecycle.c, for example, the thread that had run thread_routine will be recycled by the time the main thread returns from the pthread_join call at line 23.

Recycling releases any system or process resources that weren't released at termination. That includes the storage used for the thread's return value, the stack, memory used to store register state, and so forth. Some of these resources may have been released at termination; it is important to remember that none of it should be accessed from any other thread after termination. For example, if a thread passes a pointer to its stack storage to another thread through shared data, you should treat that information as obsolete from the time the thread that owns the stack terminates.

3 Synchronization

'That's right!' said the Tiger-lily. 'The daisies are worst of all. When one speaks, they all begin together, and it's enough to make one wither to hear the way they go on!'

Lewis Carroll, Through the Looking-Glass
Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату