并发机制之线程(1)

并发机制之线程

MacOs

11 .1 Introduction

we discussed processes in earlier chapters . We learned aboutthe environment of a Unix process , the relationships betweenprocesses , and ways to control processes . We saw that a limitedamount of sharing can occur between related processes .

In the chapter , we'll look inside a process further to see howwe use multiple threads of control( or simply threads) to performmultiple task within the environment of single process . Allthreads within a single process have access to the same processcomponents , such as file descriptors and memory .

Any time you try to share a single resource among multiple users, you have to deal with consistency . We'll conclude the chapterwith a look at the synchronization mechanisms (同步机制)available toprevent multiple threads from viewing inconsistencies in theirshared resources .

11.2 Thread Concepts

A typical UNIX process can be thought of as having a singlethread of control: each process is doing only one thing at a time.With multiple threads of control, we can design our programs to domore than one thing at a time within a single process, with eachthread handling a separate task. This approach can have severalbenefits.

· We can simplify code that deals with asynchronous events byassigning a separate thread to handle each event type. Each threadcan then handle its event using a synchronous programming model. Asynchronous programming model is much simpler than anasynchronous(异步) one.

· Multiple processes have to use complex mechanismsprovided by the operating system to share memory and filedescriptors, as we will see in Chapters 15 and 17.Threads, on the other hand, automatically have access to the samememory address space and file descriptors.

· Some problems can be partitioned so that overall programthroughput can be improved. A single process that has multipletasks to perform implicitly serializes those tasks, because thereis only one thread of control. With multiple threads of control,the processing of independent tasks can be interleaved by assigninga separate thread per task. Two tasks can be interleaved only ifthey don't depend on the processing performed by each other.

· Similarly, interactive programs can realize improved responsetime by using multiple threads to separate the portions of theprogram that deal with user input and output from the other partsof the program.

The threads interface we're about to see is from POSIX.1-2001.The threads interface, also known as"pthreads" for"POSIX threads," is an optionalfeature in POSIX.1-2001. The feature test macro for POSIX threadsis _POSIX_THREADS. Applications can either use this in an #ifdeftest to determine at compile time whether threads are supported orcall sysconf with the _SC_THREADS constant to determine at runtimewhether threads are supported.

11 .3 Thread Identification

every thread has a thread ID , Unlike the process ID , which isunique in the system , the thread Id has significance only with thecontext of the process to which it belongs .

Recall that a process ID , represented by (表示) the pid_t datatype , is a non-negative integer , A thread ID is represented bythe pthread_t data type . Implements are allowed to use structureto represented the pthread_t data type ,so portable Implementscan't treat them as Integers .

Therefore , a function must be used to compare two thread IDs.

#include <pthread.h>

int pthread_equal(pthread_t tid1, pthread_t tid2);

Returns: nonzero if equal, 0 otherwise

Linux 2.4.22 uses an unsigned long integer for the pthread_tdata type. Solaris 9 represents the pthread_t data type as anunsigned integer. FreeBSD 5.2.1 and Mac OS X 10.3 use a pointer tothe pthread structure for the pthread_t data type.

A consequence of allowing the pthread_t data type to be astructure is that there is no portable way to print its value.Sometimes, it is useful to print thread IDs during programdebugging, but there is usually no need to do so otherwise. Atworst, this results in nonportable debug code, so it is not much ofa limitation.

A thread can obtain its own thread ID by calling thepthread_self function .

#include<pthread.h>

pthread_t pthread_self( void ) ;

returns : the thread ID of the calling thread

This function can be used with pthread_equal when a thread needsto identify data structures that are tagged with its thread ID. Forexample, a master thread might place work assignments on a queueand use the thread ID to control which jobs go to each workerthread. This is illustrated in Figure11.1. A single master thread places new jobs on a work queue. Apool of three worker threads removes jobs from the queue. Insteadof allowing each thread to process whichever job is at the head ofthe queue, the master thread controls job assignment by placing theID of the thread that should process the job in each job structure.Each worker thread then removes only jobs that are tagged with itsown thread ID.

wps_clip_image-22552

11.4 Thread Creation

The traditional UNIX process model supports only one thread ofcontrol per process(每个进程只有一个控制线程). Conceptually, this is the sameas a threads-based model whereby each process is made up of onlyone thread. With pthreads, when a program runs, it also starts outas a single process with a single thread of control(程序运行时,他也是以单进程中的单个控制线程启动的). As the program runs, its behavior should beindistinguishable from the traditional process, until it createsmore threads of control. Additional threads can be created bycalling the pthread_create function.

#include<pthread.h>

int pthread_create( pthread_t * restrict tilp , constpthread_attr_t * restrict attr , void *(*start_rin)(void), void*restrict arg)

Returns: 0 if OK, error number on failure

The memory location pointed to by tidp is set to the thread IDof the newly created thread when pthread_create returnssuccessfully. The attr argument is used to customize various threadattributes. We'll cover thread attributes in Section 12.3,but for now, we'll set this to NULL to create a thread with thedefault attributes.

The newly created thread starts running at the address of thestart_rtn function. This function takes a single argument, arg,which is a typeless pointer. If you need to pass more than oneargument to thestart_rtn function, then you need to store them in astructure and pass the address of the structure in arg.

When a thread is created, there is no guarantee which runsfirst: the newly created thread or the calling thread. The newlycreated thread has access to the process address space and inheritsthe calling thread's floating-point environment and signal mask;however, the set of pending signals for the thread is cleared.

Note that the pthread functions usually return an error codewhen they fail. They don't set errno like the other POSIXfunctions(它们不像其他的POSIX函数一样设置errno). The per thread copy of errno isprovided only for compatibility with existing functions that useit. With threads, it is cleaner to return the error code from thefunction, thereby restricting the scope of the error to thefunction that caused it, instead of relying on some global statethat is changed as a side effect of the function.

Example

Although there is no portable way to print the thread id ,we canwrite a small test program that does , to gain some insight intohow threads work . The program in Figure 11.2 creates one threadand prints the process and thread IDs of the new thread and theinitial thread。

EG 12.2

#include<stdio.h>

#include<unistd.h>

#include<pthread.h>

pthread_t ntid ;

void printfids( const char * s )

{

pid_t pid ;

pthread_t tid ;

pid = getpid() ;

tid = pthread_self();

printf("%s pid %u tid %u(0x%x)\n",s ,(unsigned int)pid , (unsigned int)tid ,(unsigned int)tid) ;

}

void * thr_fn(void *arg)

{

printfids("new thread: ");

return ((void *) 0) ;

}

int main(void)

{

int err ;

err =pthread_create(&ntid, NULL, thr_fn, NULL);

if( err !=0 )

{

printf("can't createthread:%s\n",strerror(err));

}

printfids("main thread:");

sleep (1) ;

exit(0);

}

Program Output (Ubuntu 12.10 64bit):

macos@MacOs:~/program/thread$ ./thread

main thread:  pid 8483 tid 1493210880(0x59009700)

new thread:  pid 8483 tid 1484957440(0x5882a700)

macos@MacOs:~/program/thread$

Program Analysisc:

This example has two oddities, necessary to handle races betweenthe main thread and the new thread. (We'll learn better ways todeal with these later in this chapter.) The first is the need tosleep in the main thread. If it doesn't sleep, the main threadmight exit, thereby terminating the entire process before the newthread gets a chance to run. This behavior is dependent on theoperating system's threads implementation and schedulingalgorithms.

The second oddity is that the new thread obtains its thread IDby calling pthread_self instead of reading it out of shared memoryor receiving it as an argument to its thread-start routine. Recallthatpthread_create will return the thread ID of the newly createdthread through the first parameter (tidp). In our example,the main thread stores this in ntid, but the new thread can'tsafely use it. If the new thread runs before the main threadreturns from calling pthread_create, then the new thread will seethe uninitialized contents of ntid instead of the thread ID.

Running the program in Figure 11.2 onSolaris gives us

    $./a.out

    mainthread: pid 7225 tid 1 (0x1)

    newthread:  pid 7225 tid 4 (0x4)

As we expect, both threads have the same process ID, butdifferent thread IDs. Running the program in Figure 11.2 on FreeBSD gives us

    $./a.out

    mainthread: pid 14954 tid 134529024 (0x804c000)

    newthread:  pid 14954 tid 134530048 (0x804c400)

As we expect, both threads have the same process ID. If we lookat the thread IDs as decimal integers, the values look strange, butif we look at them in hexadecimal, they make more sense. As wenoted earlier, FreeBSD uses a pointer to the thread data structurefor its thread ID.

We would expect Mac OS X to be similar to FreeBSD; however, thethread ID for the main thread is from a different address rangethan the thread IDs for threads created with pthread_create:

    $./a.out

    mainthread: pid 779 tid 2684396012 (0xa000a1ec)

    newthread:  pid 779 tid 25166336 (0x1800200)

Running the same program on Linux gives us slightly differentresults:

    $./a.out

    newthread:  pid 6628 tid 1026 (0x402)

    mainthread: pid 6626 tid 1024 (0x400)

The Linux thread IDs look more reasonable, but the process IDsdon't match. This is an artifact of the Linux threadsimplementation, where the clone system call is used to implementpthread_create. Theclone system call creates a child process thatcan share a configurable amount of its parent's execution context,such as file descriptors and memory.

Note also that the output from the main thread appears beforethe output from the thread we create, except on Linux. Thisillustrates that we can't make any assumptions about how threadswill be scheduled.

11.4 Thread Termination

if any thread within a process calls exit , _exit, or _Exit,thenthe entire process terminates , Similarly , when the default actionis to terminate the process , a signal sent to a thread willterminate the entire process (we'll talk more about theinteractions between signals and thread in Section 12.8).

A single thread can exit in three ways , thereby stopping itsflow of control , without terminating the entire process .

1. The thread can simply return from the start routine . Thereturn value is the thread's exit code.

2. The thread can be canceled by another thread in the sameprocess.

3. The thread can call phread_exit

#include<pthread.h>

void pthread_exit(void *rval_ptr) ;

The rval_ptr is a typeless pointer , similar to the singlepassed to the start routine(以传给启动例程的单个参数相似。)。 This pointer isavailable to other threads in the process by calling thepthread_join function(进程中的其他线程可以通过调用pthread_join函数访问到这个指针).

#include<pthread.h>

int pthread_join(pthread_t thread, void **rva_ptr) ;

Returns : 0 if Ok, error number on failure

The calling thread will block until the specified thread callspthread_exit, returns from its start routine, or is canceled. Ifthe thread simply returned from its start routine(如果线程只是从他的启动例程返回), rval_ptr will contain the return code. If thethread was canceled, the memory location specified (指定的内存单元)byrval_ptr is set to PTHREAD_CANCELED.

By calling pthread_join, we automatically place a thread in thedetached state(分离状态) (discussed shortly) so that its resources canbe recovered. If the thread was already in the detached state,calling pthread_joinfails, returning EINVAL.

If we're not interested in a thread's return value, we can setrval_ptr to NULL. In this case, calling pthread_join allows us towait for the specified thread, but does not retrieve the thread'stermination status.(调用pthread_join函数将等待指定的线程终止,但不获取线程的终止状态)

Example

Figure 11.3 shows how to fetch (取得) code from the exit code froma thread that has terminated。

EG 11.3

#include<unistd.h>

#include<stdio.h>

#include<fcntl.h>

#include<error.h>

#include<pthread.h>

void * thr_fn1( void *arg)

{

printf("thread 1 returning\n");

return ((void *)1 ) ;

}

void * thr_fn2( void * arg )

{

printf("thread 2exiting\n");

pthread_exit((void *) 2  );

}

int main(void )

{

int err ;

pthread_t  tid1 ,tid2;

void *tret ;

err= pthread_create( &tid1 , NULL ,thr_fn1,NULL) ;

if( err !=0 )

{

printf("can't create thread 1:%s\n",strerror(err));

return -1 ;

}

err = pthread_create(&tid2 , NULL ,thr_fn2,NULL) ;

if(err !=0 )

{

printf("can't create thread 2 :%s\n",strerror(err));

return -1 ;

}

err = pthread_join(tid1,&tret) ;

if( err !=0 )

{

printf("can't join with thread 1 :%s\n",strerror(err));

return -1 ;

}

printf("thread 1 exit code %d\n",(int) tret);

err = pthread_join(tid2,&tret) ;

if( err !=0 )

{

printf("can't join with thread2 : %s\n",strerror(err));

return -1 ;

}

printf("thread 2 exit code %d\n",(int)tret);

return 0 ;

}

Program Output :

macos@MacOs:~/program/11$ ./11.3

thread 1 returning

thread 2 exiting

thread 1 exit code 1

thread 2 exit code 2

Program Analysis

As we can see, when a thread exits by calling pthread_exit or bysimply returning from the start routine, the exit status can beobtained by another thread by calling pthread_join.

The typeless pointer passed to pthread_create and pthread_exitcan be used to pass more than a single value. The pointer can beused to pass the address of astructurecontaining more complexinformation. Be careful that the memory used for the structure isstill valid when the caller has completed. If the structure wasallocated(分配) on the caller's stack, for example, the memorycontents might have changed by the time the structure is used. Forexample, if a thread allocates a structure on its stack andpasses(传递)a pointer to this structure to pthread_exit, then thestack might be destroyed and its memory reused for something elseby the time the caller of pthread_join tries to useit(又如,线程在自己的栈上分配了一个结构然后把指向这个结构的指针传递给pthread_exit,那么当调用pthread_join的线程试图使用这个结构时,这个栈有可能已经被撤销,这块内存也已另作他用).

程序清单11-3中的程序给出了用自动变量(分配在栈上)作为pthread_exit 的参数时给线问题。

void

cleanup(void*arg)

{

printf("cleanup:%s\n",(char*)arg);

}

void*

thr_fn1(void*arg)

{

printf("thread 1start\n");

pthread_cleanup_push(cleanup,"thread 1 firsthandler");

pthread_cleanup_push(cleanup,"thread 1 secondhandler");

printf("thread 1 pushcomplete\n");

if(arg)

return((void*)1);

pthread_cleanup_pop(0);

pthread_cleanup_pop(0);

return((void*)1);

}

void*

thr_fn2(void*arg)

{

printf("thread 2start\n");

pthread_cleanup_push(cleanup,"thread 2 firsthandler");

pthread_cleanup_push(cleanup,"thread 2 secondhandler");

printf("thread 2 pushcomplete\n");

if(arg)

pthread_exit((void*)2);

pthread_cleanup_pop(0);

pthread_cleanup_pop(0);

pthread_exit((void*)2);

}

int

main(void)

{

int err;

pthread_t tid1,tid2;

void*tret;

err=pthread_create(&tid1,NULL,thr_fn1,(void*)1);if(err!=0)

err_quit("can't create thread1:%s\n",strerror(err));

err=pthread_create(&tid2,NULL,thr_fn2,(void*)1);

if(err!=0)

err_quit("can't create thread2:%s\n",strerror(err));

err=pthread_join(tid1,&tret);

if(err!=0)

err_quit("can't join with thread1:%s\n",strerror(err));

printf("thread 1 exitcode%d\n",(int)tret);

err=pthread_join(tid2,&tret);

if(err!=0)

err_quit("can't join with thread2:%s\n",strerror(err));

printf("thread 2 exitcode%d\n",(int)tret);

exit(0);

}

By Now , you should begin to see similarities between thethread functions and the process functions . Figure 11.6 summarizesthe similar functions

wps_clip_image-9950

By default,a thread's termination status is retained untilpthread_join is called for that thread.

A thread's underlying storage can be reclaimed immediately ontermination if that thread has been detached(分离).When a thread isdetached,the pthread_join function can't be used to wait for itstermination status.A call to pthread_join for a detached threadwill fail,returning EINVAL. We can detach a thread by callingpthread_detach.

///

#include<pthread.h>

Int pthread_detach(pthread_t tid);

Returns: 0 if Ok ,error number on failure

///

As we will see in the next chapter,we can create a thread thatis already in the detached state

by modifying the thread attributes we pass topthread_create.

11 .6 Thread Synchronization

Figure 11.7 shows a hypothetical example of two threads readingand writing the same variable.In this example,thread A reads thevariable and then writes a new value to it,but the write operationtakes two memory cycles.If thread B reads the same variable betweenthe two write cycles,it will see an inconsistent value.

wps_clip_image-10768

wps_clip_image-18396

You also need to synchronize two or more threads that might tryto modify(修改) the same variable at the same time。Consider the casein which you increment a variable (Figure 11.9).The incrementoperation is usually broken down into three steps

1. Read the memory location into a register

2. Increment the value in register

3. Writer the new value back to the memory location

Mutexes

We can protect our data and ensure access by only one thread ata time by using the pthreads mutual-exclusion interfaces(互斥接口)。

wps_clip_image-22147

A mutex variable is represented by the pthread_mutex_t datatype.Before we can use a mutex variable,we must first initialize itby either setting it to the constant PTHREAD_MUTEX_INITIALIZER(forstatically-allocated mutexes only)or calling pthread_mutex_init.Ifwe allocate the mutex dynamically(by calling malloc,for example),then we need to call pthread_mutex_destroy before freeing thememory.

#include<pthread.h>

int pthread_mutex_init(pthread_mutex_t*restrict mutex, constpthread_mutexattr_t *restrict attr);

int pthread_mutex_destroy(pthread_mutex_t*mutex);

Both return:0 if OK,error number on failure

To initialize a mutex with the default attributes ,we set attrto NULL。We will discuss noodefault mutex attributes in Section12.4

#include<pthread.h>

int pthread_mutex_lock(pthread_mutex_t*mutex);

int pthread_mutex_trylock(pthread_mutex_t*mutex);

int pthread_mutex_unlock(pthread_mutex_t*mutex);

All return:0 if OK,error number on failure

If a thread can't afford to block,it can usepthread_mutex_trylock to lock the mutex conditionally.If the mutexis unlocked at the time pthread_mutex_trylock is called,thenpthread_mutex_trylock will lock the mutex without blocking andreturn 0.Otherwise,

pthread_mutex_trylock will fail,returning EBUSY without lockingthe mutex.

Example

Figure 11.10 illustrates(举例说明) a mutex used to protect a datastructure.When more than one thread needs to access adynamically-allocated object,we can embed a reference(引用) count inthe object ensure that we don't free its memory before all threadsare done using it.

We lock the mutex before incrementing the referencecount,decrementing the reference count

and checking whether the reference count reaches zero.No lockingis necessary when we

initialize the reference count to 1 in the foo_allocfunction,because the allocating thread(分配线程) is the only referenceto it so far.If we were to place the structure on a list at thispoint,it could be

found by other threads,so we would need to lock it first.

Before using the object,threads are expected to add a referencecount to it.When they are

done,they must release the reference.When the last reference isreleased,the object's memo

is freed.

EG 11.10

#include <stdlib.h>

#include <pthread.h>

struct foo {

int            f_count;

pthread_mutex_t f_lock;

};

struct foo *

foo_alloc(void)

{

struct foo *fp;

if ((fp = malloc(sizeof(struct foo))) != NULL) {

fp->f_count = 1;

if(pthread_mutex_init(&fp->f_lock,NULL) != 0) {

free(fp);

return(NULL);

}

}

return(fp);

}

void

foo_hold(struct foo *fp)

{

pthread_mutex_lock(&fp->f_lock);

fp->f_count++;

pthread_mutex_unlock(&fp->f_lock);

}

void

foo_rele(struct foo *fp)

{

pthread_mutex_lock(&fp->f_lock);

if (--fp->f_count == 0) {

pthread_mutex_unlock(&fp->f_lock);

pthread_mutex_destroy(&fp->f_lock);

free(fp);

} else {

pthread_mutex_unlock(&fp->f_lock);

}

}

Deadlock Avoidance

wps_clip_image-6110

Example

In this example , we update Figure 11.10 to show the use of twomutexes, we avoid deadlocks by ensuring that when we need toacquire two mutexes at the same time , we always lock them in thesame order(顺序)。The second mutex protects a bash list that we use to keep track of (跟踪)the foo data structures.Thus, the hashlock mutex protects both the fh hash table and thef_next hash link field in the foo structure. The f_lock mutex inthe foo structure protects access to the remainder of the foostructure's fields.

ReaderWriter Locks

wps_clip_image-23435

Readerwriter locks are similar to mutexes, exceptthat they allow for higher degrees of parallelism. With a mutex,the state is either locked or unlocked, and only one thread canlock it at a time. Three states are possible with a readerwriterlock: locked in read mode, locked in write mode, and unlocked. Onlyone thread at a time can hold a readerwriter lock in write mode,but multiple threads can hold a readerwriter lock in read mode atthe same time.

When a readerwriter lock is write-locked, all threads attemptingto lock it block until it is unlocked. When a readerwriter lock isread-locked, all threads attempting to lock it in read mode aregiven access, but any threads attempting to lock it in write modeblock until all the threads have relinquished their read locks.Although implementations vary, readerwriter locks usually blockadditional readers if a lock is already held in read mode and athread is blocked trying to acquire the lock in write mode. Thisprevents a constant stream of readers from starving waitingwriters.

Readerwriter locks are well suited for situations in which datastructures are read more often than they are modified. When areaderwriter lock is held in write mode, the data structure itprotects can be modified safely, since only one thread at a timecan hold the lock in write mode. When the readerwriter lock is heldin read mode, the data structure it protects can be read bymultiple threads, as long as the threads first acquire the lock inread mode.

Readerwriter locks are also called sharedexclusivelocks. When a readerwriter lock is read-locked, it is said to belocked in shared mode. When it is write-locked, it is said to belocked in exclusive mode.

As with mutexes, readerwriter locks must be initialized beforeuse and destroyed before freeing their underlying memory.

#include<pthread.h>

Int pthread_rwlock_init( pthread_rwlock_t*restrict  rwlock, const pthread_rwlockattr_t*restrict attr)

Int pthread_rwlock_destroy(pthread_rwlock_t *rwlock) ;

Both return: 0 if OK, error number on failure

To lock a readerwriter lock in read mode, we callpthread_rwlock_rdlock. To write-lock a readerwriter lock, we callpthread_rwlock_wrlock. Regardless of how we lock a readerwriterlock, we can call pthread_rwlock_unlock to unlock it.

#include <pthread.h>

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

All return: 0 if OK, error number on failure

Single Unix Specification also defines conditional version ofthe readerwriter locking primitives

#include <pthread.h>

int pthread_rwlock_tryrdlock(pthread_rwlock_t

wps_clip_image-18998

*rwlock);

int pthread_rwlock_trywrlock(pthread_rwlock_t

wps_clip_image-7760

*rwlock);

Both return: 0 if OK, error number on failure

When the lcok can be acquired, these functions return 0 .Otherwise ,they return the error EBUSY.These functions can be usedin situations in which confirming to a lock hierarchy isn't enoughto avoid a deadlock, as we discussedperviously.(这些函数可以用于前面讨论的遵守某种锁层次但还不能完全避免死锁的情况)

Example

The program in Figure 11.13§illustrates the use of readerwriter locks. A queue of job requestsis protected by a single readerwriter lock. This example shows apossible implementation of Figure 11.1§,whereby multiple worker threads obtain jobs assigned to them by asingle master thread.

In this example, we lock the queue's readerwriterlock in write mode whenever we need to add a job to the queue orremove a job from the queue. Whenever we search the queue, we grabthe lock in read mode, allowing all the worker threads to searchthe queue concurrently. Using a readerwriter lock will improveperformance in this case only if threads search the queue much morefrequently than they add or remove jobs.

The worker threads take only those jobs that match their threadID off the queue. Since the job structures are used only by onethread at a time, they don't need any extra locking.

EG 11.13:


#include <stdlib.h>
#include <pthread.h>

int insert  =0 ; 
struct job {
struct job *j_next;
struct job *j_prev;
pthread_t  j_id;  

};
struct QUEUE {
structjob     *q_head;
structjob     *q_tail;
pthread_rwlock_t q_lock;
};
struct arg{
struct QUEUE queue_work ;
pthread_t id; 
};
int queue_init(struct QUEUE *qp)
{
int err;
qp->q_head = NULL;
qp->q_tail = NULL;
err =pthread_rwlock_init(&qp->q_lock,NULL);
if (err != 0)
return(err);

return(0);
}

void
job_insert(struct QUEUE *qp, struct job *jp)
{
printf("Insert %d....\n",++insert);
pthread_rwlock_wrlock(&qp->q_lock);
jp->j_next = qp->q_head;
jp->j_prev = NULL;
if (qp->q_head != NULL)
qp->q_head->j_prev = jp;
else
qp->q_tail = jp;
qp->q_head = jp;
pthread_rwlock_unlock(&qp->q_lock);
printf("finish ....\n");
}

void
job_append(struct QUEUE *qp, struct job *jp)
{
pthread_rwlock_wrlock(&qp->q_lock);
jp->j_next = NULL;
jp->j_prev = qp->q_tail;
if (qp->q_tail != NULL)
qp->q_tail->j_next = jp;
else
qp->q_head = jp;
qp->q_tail = jp;
pthread_rwlock_unlock(&qp->q_lock);
}

void
job_remove(struct QUEUE *qp, struct job *jp)
{
pthread_rwlock_wrlock(&qp->q_lock);
if (jp == qp->q_head) {
qp->q_head = jp->j_next;
if (qp->q_tail == jp)
qp->q_tail = NULL;
} else if (jp == qp->q_tail) {
qp->q_tail = jp->j_prev;
if (qp->q_head == jp)
qp->q_head = NULL;
} else {
jp->j_prev->j_next =jp->j_next;
jp->j_next->j_prev =jp->j_prev;
}
pthread_rwlock_unlock(&qp->q_lock);
}

struct job *job_find(struct QUEUE *qp, pthread_t id)
{
struct job *jp;
if(pthread_rwlock_rdlock(&qp->q_lock)!= 0)
return(NULL);
for (jp = qp->q_head; jp != NULL; jp =jp->j_next)
if (pthread_equal(jp->j_id, id))
{
printf("Thread 0x%x start to woking\n",(unsigned int) id);
printf("Thread 0x%x is woking\n",(unsigned int)id);
printf("Thread 0x%x finish woking\n", (unsigned int)id);
break;
}
pthread_rwlock_unlock(&qp->q_lock);
return(jp);
}
void thr_( void *_arg )
{
printf("Thread start...\n");
struct arg  *a = (struct arg *)(_arg);
pthread_t ptid  = a->id ;
struct QUEUE queue_work = a ->queue_work;
job_find(&queue_work, ptid);
}
int main( void ){
struct QUEUE *queue_work ;
struct arg *a ; 
struct job *jp1,*jp2,*jp3;
pthread_t ptid1,ptid2,ptid3 ;
int err ;
a = (struct arg * ) malloc (sizeof(struct arg)); 
queue_work=(struct QUEUE *) malloc(sizeof(struct QUEUE)) ;
jp1 = (struct job *) malloc(sizeof(struct job)) ;
jp2 = (struct job *) malloc(sizeof(struct job)) ;
jp3 = (struct job *) malloc(sizeof(struct job)) ;
queue_init(queue_work) ;
jp1->j_id = ptid1 ;
jp2->j_id = ptid2 ;
jp3->j_id = ptid3 ;
job_insert(queue_work, jp1);
job_insert(queue_work, jp2);
job_insert(queue_work, jp3);
a->queue_work = *queue_work ;
a->id = ptid1 ;
err =pthread_create(&ptid1,NULL,&thr_,a);
if(err!=0)
{
printf("Thread 1failure\n");
return -1 ;
}
a->id = ptid2 ;
err =pthread_create(&ptid2,NULL,&thr_,a);
if(err!=0)
{
printf("Thread 2failure\n");
return -1 ;
}
a->id = ptid3 ;
err =pthread_create(&ptid3,NULL,&thr_,a);
if(err!=0)
{
printf("Thread 1failure\n");
return -1 ;
}
sleep(1);
//   start to runthread 
return 0 ;
}

Program Output :

Program Analysis:

Condition Variables

    Condition variables are another synchronization mechanism availableto threads. Condition variables provide a place for threads torendezvous. When used with mutexes, condition variables allowthreads to wait in a race-free way for arbitrary conditions tooccur.(允许线程以无竞争的方式等待特定条件的发生)。

    The condition itself is protected by a mutex 。A thread must firstlock the mutex to change the condition state. Other threads willnot notice the change until acquire the mutex,because the mutexmust be locked to able to evaluate the condition。

    We can use the pthread_mutex_destroy function to deinitialize acondition variable before freeing its underlying memory

    we can use the pthread_mutex_destroy function to deinitialize acondition variable before freeing its underlying memory.

#include<pthread.h>

int p#include <pthread.h>

int pthread_cond_init(pthread_cond_t *restrict cond,

                     pthread_condattr_t *restrict attr);

int pthread_cond_destroy(pthread_cond_t *cond);

Both return: 0 if OK, error number on failure

    We use pthread_cond_wait to wait for a condition to be true. Avariant is provided to return an error code if the condition hasn'tbeen satisfied in the specified amount of time.

#include <pthread.h>

int pthread_cond_wait(pthread_cond_t *restrictcond,                    pthread_mutex_t *restrict mutex);

int pthread_cond_timedwait(pthread_cond_t *restrictcond,                      pthread_mutex_t *restrict mutex, const struct timespec *restricttimeout);

Both return: 0 if OK, error number on failure

The mutex passed to pthread_cond_wait protects the condition.The caller passes it locked to the function, which then atomicallyplaces the calling thread on the list of threads waiting for thecondition and unlocks the mutex. This closes the window between thetime that the condition is checked and the time that the threadgoes to sleep waiting for the condition to change, so that thethread doesn't miss a change in the condition. Whenpthread_cond_wait returns, the mutex is again locked.

The pthread_cond_timedwait function works the same as thepthread_cond_wait function with the addition of the timeout. Thetimeout value specifies how long we will wait. It is specified bythe timespec structure, where a time value is represented by anumber of seconds and partial seconds. Partial seconds arespecified in units of nanoseconds:

    structtimespec {

           time_t tv_sec;  

           long  tv_nsec; 

    };

Using this structure, we need to specify how long we are willingto wait as an absolute time instead of a relative time. Forexample, if we are willing to wait 3 minutes, instead oftranslating 3 minutes into a timespec structure, we need totranslate now + 3 minutes into a timespec structure.

We can use gettimeofday (Section 6.10) to get the currenttime expressed as a timeval structure and translate this into atimespec structure. To obtain the absolute time for the timeoutvalue, we can use the following function:

   void

   maketimeout(struct timespec*tsp, long minutes)

   {

       struct timeval now;

       

       gettimeofday(&now);

       tsp->tv_sec = now.tv_sec;

       tsp->tv_nsec = now.tv_usec * 1000;

       

       tsp->tv_sec += minutes * 60;

   }

    If the timeout expires without the condition occurring,pthread_cond_timedwait will reacquire the mutex and return theerror ETIMEDOUT. When it returns from a successful call topthread_cond_wait or pthread_cond_timedwait, a thread needs toreevaluate the condition, since another thread might have run andalready changed the condition.

     There are two functions to notify threads that a condition has beensatisfied. The pthread_cond_signal function will wake up one threadwaiting on a condition, whereas the pthread_cond_broadcast functionwill wake up all threads waiting on a condition.

The POSIX specification allows for implementations ofpthread_cond_signal to wake up more than one thread, to make theimplementation simpler.

#include<pthread.h>

int pthread_cond_signal(pthread_cond_t *cond);

int pthread_cond_broadcast(pthread_cond_t *cond);

Both return: 0 if OK, error number on failure

When we call pthread_cond_signal or pthread_cond_broadcast, weare said to be signaling the thread or condition. We have to becareful to signal the threads only after changing the state of thecondition.

Example

Figure 11.14 shows an example of how touse condition variables and mutexes together to synchronizethreads.

The condition is thestate of the work queue. We protect the condition with a mutex andevaluate the condition in a while loop. When we put a message onthe work queue, we need to hold the mutex, but we don't need tohold the mutex when we signal the waiting threads. As long as it isokay for a thread to pull the message off the queue before we callcond_signal, we can do this after releasing the mutex. Since wecheck the condition in a while loop, this doesn't present aproblem: a thread will wake up, find that the queue is still empty,and go back to waiting again. If the code couldn't tolerate thisrace, we would need to hold the mutex when we signal thethreads.

Figure 11.14. Usingcondition variables

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值