Linux系统编程学习笔记(十一)线程

线程

线程和进程类似,但是线程之间能够共享更多的信息。一个进程中的所有线程可以共享进程文件描述符和内存。有了多线程控制,我们可以把我们的程序设计成为在一个进程同时做多个任务,每一个线程做一个独立的任务,这种方式可以有以下好处:

  1. 通过把每一个事件分配给一个线程处理,可以简化异步事件处理的代码。每一个线程可以用同步编程模型,而同步编程要比异步编程简单的多。
  2. 多个进程需要使用复杂的机制来共享内存和文件描述符。而线程可以自动共享同一内存地址空间和文件描述符。
  3. 有一些问题可以划分以便提高这个程序的吞吐量。一个进程如果有多个任务,需要进行隐式的序列化任务,因为只有一个线程控制。使用多线程控制,独立的任务可以将每个任务分配一个线程。
  4. 交互式的进程可以改善响应时间,通过使用多线程将I/O和程序其他部分分开实现处理。

多线程不光可以在多核系统中得到并行的优势,而且在单核系统中,也可以提高系统的吞吐量和响应时间,因为当一个线程阻塞的时候,另一线程可以占有cpu执行。

线程有一些描述线程和执行环境的信息来表示它,包括线程ID,寄存器值的集合,栈,调度优先级和策略,信号的掩码,errno变量以及线程特有的一些结构。进程中各个线程共享进程的程序执行文本,程序的全局变量、堆内存、栈和文件描述符。

1、线程标志:

和进程一样,每一个线程都有一个ID。和进程ID是全系统唯一不同,线程ID是在进程内唯一。进程id用pid_t类型来表示,是一个非负的整数。线程ID由pthread_t数据类型代表,和进程一样实现可能为一个结构,所以把pthread_t类型当做一个整数是不具有可移植性,所以也没有可移植的打印线程id的方法。这样也需要一个函数来比较两个线程的ID是否相同。

#include <pthread.h>  
  
int pthread_equal(pthread_t tid1, pthread_t tid2);  

一个线程可以获得使用pthread_self来获得自己的线程id:

#include <pthread.h>  
  
pthread_t pthread_self();  

这个方法 可以和pthread_equal配合使用,来识别被打上线程标记的数据结构。

2、线程创建:

通过调用pthread_create可以创建一个线程:

#include <pthread.h>  
  
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *),void *restrict arg);  

创建成功tidp返回线程的id,attr为线程的属性,新创建的进程会运行start_rtn函数,并传入arg作为参数。如果想传入多个参数到start_rtn函数中,需要将它们存储在一个结构体中,并把地址传到arg中。失败返回error code,而它们不设定errno。每个线程一个errno只是为了兼容以前的函数而被使用的。多线程中,返回error code要比依赖于全局变量的errno清晰一些。

例子:创建一个线程,并打印进程id、新创建的线程id和主线程id。

#include <pthread.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
  
pthread_t ntid;  
  
void printids(const char *s){  
    pid_t pid;  
    pthread_t tid;  
    pid = getpid();  
    tid = pthread_self();  
    printf("%s pid %u tid (0x%x) \n",s,(unsigned int)pid,(unsigned int)tid);  
}  
  
void * thr_fn(void *arg){  
    printids("new thread: ");  
    return ((void *) 0);  
}  
  
int main(void){  
    int err;  
    err = pthread_create(&ntid, NULL, thr_fn, NULL);  
    if(err != 0){  
        fprintf(stderr,"create pthread failed: %s",strerror(err));  
        exit(1);  
    }  
    printids("main thread: ");  
    sleep(1);  
    exit(0);  
}  

编译gcc test.c -lpthread

这个例子有两个地方比较古怪,主要是为了处理主线程和新创建线程的竞争:

1)主线程休眠,以防止主线程终止,导致整个进程的终止,新建的线程没有机会运行,我们后面介绍pthread_join可以避免这个。

2)新的线程获得它的线程id是通过调用pthread_self而不是读取一个共享内存的变量或者传递的参数。这是因为主线程不能安全的使用ntid,新的线程可能在调用pthread_create返回之前开始运行。

3、线程终止

如果进程内的任何一个线程调用exit_Exit,_exit,整个进程就会终止。类似的如果信号的默认action是终止进程,那么一个发送到线程的信号会终止整个进程。

只终止一个线程有三种方式:

1、线程简单的返回。返回值就是退出码。

2、线程可以被进程中的另一个线程取消。

3、线程调用pthread_exit。

#include <phtread.h>  
  
void pthread_exit(void *rval_ptr); 

rval_ptr是一个无类型的指针,和传递到进程的单个参数类似。这个指针可以被调用pthread_join的其他的线程得到。

#include <pthread.h>  
  
int pthread_join(pthread_t thread,void **rval_ptr);  

调用pthread_join的线程会阻塞,直到指定的线程调用了pthread_exit,从start_rtn即线程执行函数中返回,或者被取消。

如果线程简单的返回,那么rval_ptr被设置成start_rtn的返回值,如果线程被取消,rval_ptr被设置成PTHREAD_CANCELED。

通过调用pthread_join,我们自动将线程设置成为detached状态,所有资源会被清除。如果线程已经处于detached状态,那么pthread_join就会失败,返回EINVAL.如果我们不关心线程的返回值,那么我们可以把rval_ptr设置为NULL。

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <pthread.h>  
  
void * thr_fun1(void *arg){  
    printf("Thread 1 returning...\n");  
    return ((void *)1);  
}  
  
void * thr_fun2(void *arg){  
    printf("Thread 2 exiting...\n");  
    pthread_exit((void *) 2);  
}  
  
int main(void){  
    int err;  
    pthread_t tid1,tid2;  
    void *tret;  
  
    err = pthread_create(&tid1,NULL,thr_fun1,NULL);  
    if(err != 0){  
        fprintf(stderr,"create thread1 failed: %s",strerror(err));  
        exit(1);  
    }  
    err = pthread_create(&tid2,NULL,thr_fun2,NULL);  
    if(err != 0){  
        fprintf(stderr,"create thread2 failed: %s",strerror(err));  
        exit(1);  
    }  
    err = pthread_join(tid1,&tret);  
    if( err != 0 ){  
        fprintf(stderr,"join thread1 failed: %s",strerror(err));  
        exit(1);  
    }  
      
    printf("Thread 1 exit code %d\n",(int)tret);  
  
    err = pthread_join(tid2,&tret);  
    if( err != 0 ){  
        fprintf(stderr,"join thread2 failed: %s",strerror(err));  
        exit(1);  
    }  
    printf("Thread 2 exit code %d\n",(int)tret);  
    exit(0);  
}  

无类型的指针传递给pthread_create和pthread_exit,使用它可以传递多个值,这个指针可以指向包含复杂的结构。但是需要注意这个结构在调用返回时仍然合法。如果这个结构是在调用者的栈中,内存的内容在使用的可能时候已经被改变。比如一个线程申请在栈中申请了一个结构,然后将结构的指针传递给pthread_exit,接着这个栈在调用thread_join的时候可能已经被销毁。

例子:

#include <stdio.h>  
#include <stdlib.h>  
#include <pthread.h>  
#include <string.h>  
  
struct foo{  
    int a,b,c,d;  
};  
  
void printfoo(const char *s, const struct foo *fp){  
    printf("%s",s);  
    printf(" structure at 0x%x\n",(unsigned) fp);  
    printf(" foo.a = %d\n", fp->a);  
    printf(" foo.b = %d\n", fp->b);  
    printf(" foo.c = %d\n", fp->c);  
    printf(" foo.d = %d\n", fp->d);  
}  
  
void *thr_fn1(void *arg){  
    struct foo foo = {1,2,3,4};  
    printfoo("thread 1:\n",&foo);  
    pthread_exit((void *)&foo);  
}  
  
void *thr_fn2(void *arg){  
    printf("thread 2: ID is %u\n",(unsigned)pthread_self());  
    pthread_exit((void *)0);  
}  
  
int main(void){  
    int err;  
    pthread_t tid1,tid2;  
    struct foo *fp;  
  
    err = pthread_create(&tid1,NULL,thr_fn1,NULL);  
    if(err != 0){  
        fprintf(stderr,"create thread1 failed: %s",strerror(err));  
        exit(1);  
    }  
    err = pthread_join(tid1,(void *)&fp);  
    if(err != 0){  
        fprintf(stderr,"thread_join failed: %s",strerror(err));  
        exit(1);      
    }  
    sleep(1);  
    printf("Parent starting second thread\n");  
  
    err = pthread_create(&tid2,NULL,thr_fn2,NULL);  
    if(err != 0){  
        fprintf(stderr,"create thread2 failed: %s",strerror(err));  
        exit(1);  
    }  
    sleep(1);  
    printfoo("Parent:\n",fp);  
    exit(0);  
}  

编译运行结果

m@ubuntu:~/test$ gcc test.c -lpthread 
m@ubuntu:~/test$ ./a.out 
thread 1:
 structure at 0x6baceed0
 foo.a = 1
 foo.b = 2
 foo.c = 3
 foo.d = 4
Parent starting second thread
thread 2: ID is 1806497536
Parent:
 structure at 0x6baceed0
 foo.a = 1810593632
 foo.b = 32595
 foo.c = 1808188898
 foo.d = 32595

可见由于子线程1的fp结果被销毁了,所以父线程去读的时候已经是随机值了。

一个线程可以可以使用pthread_cancel来取消同一进程中的其他线程。

#include <pthread.h>  
  
int pthread_cancel(pthread_t tid); 

在默认的条件下,pthread_cancle将会使由tid指定的线程像调用了pthread_exit(PTHREAD_CANCELED)一样,但是一个线程可以选择忽略和如果控制被取消。pthread_cancel并不等待线程的终止,而只是发送一个请求。

4、线程注册清理函数

一个线程可以注册函数,当它终止的时候被调用,这个和进程使用atexit注册函数,当进程终止的时候调用类似。这个函数比较出名的就是线程清理函数。一个线程可以加入多个线程清理函数,这个清理函数保存在栈中,所以执行的顺序和注册的顺序相反:

#include <pthread.h>  
  
void pthread_cleanup_push(void (*rtn)(void *), void arg);  
void pthread_cleanup_pop(int execute);  

pthread_cleanup_push来注册清理函数rtn,这个函数有一个参数arg。但一下三种情形之一发生时,注册的清理函数被执行:

1)调用pthread_exit

2)作为对取消线程请求(pthread_cancel)的响应。

3)以非0参数调用pthread_cleanup_pop,会调用清除函数,同时也会清除掉注册的清除函数。

如果pthread_cleanup_pop被传递0参数,则清除函数不会被调用,但是仍然会清除处于栈顶的清理函数。

一个限制是这两个函数可能被实现为一个宏,所以在线程的同一作用域必须以匹配的成对出现。pthread_cleanup_push可能有{,而pthread_cleanup_pop可能有匹配这个字符的}字符。

下面看一个精心构造的例子

#include <stdio.h>  
#include <stdlib.h>  
#include <pthread.h>  
#include <string.h>  
  
void cleanup(void *arg){  
    printf("cleanup: %s\n",(char *)arg);  
}  
  
void *thr_fn1(void *arg){  
    printf("thread 1 start\n");  
    pthread_cleanup_push(cleanup,"thread 1 first handler");  
    pthread_cleanup_push(cleanup,"thread 1 second handler");  
    printf("thread 1 push complete\n");  
    if(arg)  
        return ((void *)1);  
    pthread_cleanup_pop(1);  
    pthread_cleanup_pop(1);  
    return ((void *)1);  
}  
  
void *thr_fn2(void *arg){  
    printf("thread 2 start\n");  
    pthread_cleanup_push(cleanup,"thread 2 first handler");  
    pthread_cleanup_push(cleanup,"thread 2 second handler");  
    printf("thread 2 push complete\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){  
        fprintf(stderr,"create thread1 failed: %s",strerror(err));  
        exit(1);  
    }  
  
    err = pthread_create(&tid2,NULL,thr_fn2,(void *)2);  
    if(err != 0){  
        fprintf(stderr,"create thread 2 failed: %s",strerror(err));  
        exit(1);  
    }  
  
    err = pthread_join(tid1,&tret);  
    if(err != 0){  
        fprintf(stderr,"thread1 join failed: %s",strerror(err));  
        exit(1);  
    }  
    printf("thread 1 exit code %d\n",(int)tret);  
    err = pthread_join(tid2,&tret);  
    if(err != 0){  
        fprintf(stderr,"thread2 join failed: %s",strerror(err));  
        exit(1);  
    }  
    printf("thread 2 exit code %d\n",(int) tret);  
    exit(0);  
  
}  

编译运行结果如下

m@ubuntu:~/test$ gcc test.c -lpthread 
m@ubuntu:~/test$ ./a.out 
thread 1 start
thread 1 push complete
thread 1 exit code 1
thread 2 start
thread 2 push complete
cleanup: thread 2 second handler
cleanup: thread 2 first handler
thread 2 exit code 2

观察到thread1并没有执行清理函数,但thread2执行了。原因在于:如果线程从开始例程(start routine)(也就是该线程的执行函数)中返回(by return statement),清理函数不会被调用。
由于给thr_fn1传递的参数是非0的,所以直接return,而不是调用pthread_exit,同时以1作为非0参数的pthread_cleanup_pop也不会执行,所以线程1无法执行清除函数。但线程2是按照pthread_exit来退出的,所以可以执行清除函数。

把上面例子中的

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

改成

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

再次执行,会发现线程2即使按照pthread_exit退出,也不会再执行清除函数,因为前面两次以0调用pthread_cleanup_pop清除了注册的函数。结果如下

m@ubuntu:~/test$ ./a.out 
thread 1 start
thread 1 push complete
thread 1 exit code 1
thread 2 start
thread 2 push complete
thread 2 exit code 2

再来看一个cancel导致的调用,这个时候即使以0调用pthread_clean_pop还是会执行清除函数。

#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
void clean_fun1(void * arg)
{
    printf("this is clean fun1\n");
}
void clean_fun2(void * arg)
{
    printf("this is clean fun2\n");
}
void * thread_fun(void * arg)
{
    pthread_cleanup_push(clean_fun1,NULL);
    pthread_cleanup_push(clean_fun2,NULL);
    sleep(100);
    pthread_cleanup_pop(0);
    pthread_cleanup_pop(0);
    return NULL;
}
int main()
{
    pthread_t tid1;
    int err;
    err=pthread_create(&tid1,NULL,thread_fun,NULL);
    if(err!=0)
    {
        perror("pthread_create");
        exit(0);
    }
    sleep(3);
    err=pthread_cancel(tid1);
    if(err!=0)
    {
        perror("cancel error:");
        exit(0);
    }
    err=pthread_join(tid1,NULL);
    if(err!=0)
    {
        perror("pthread_join error:");
        exit(0);
    }
 
    return 0;
}

编译执行结果

m@ubuntu:~/test$ gcc test.c -lpthread
m@ubuntu:~/test$ ./a.out 
this is clean fun2
this is clean fun1

总结:一个是线程没有cancel、在中间调用pthread_exit终止的话一直运行到pop函数那里,此时pop函数的execute设置为0,只会从清理函数堆栈取一个清理函数出来进行删除,不会调用执行,若execute设置为非0就会从清理函数堆栈中取出一个清理函数,并调用执行,执行完后删除。若一个线程有cancel、pthread_exit终止,不管你那个execute有没有设置为0,系统都会自动从清理函数堆栈中取出所有的清理函数进行调用执行,并执行完后进行删除(之所以全部调用,因为pop和push是成对出现,这是因为它的宏定义。)

线程的终止状态,直到pthread_join被调用的时候才能得到。如果一个线程已经被detached,这个线程的空间将会被回收。pthread_join不能等待detached的线程,获得其终止状态。pthread_join一个detached线程将会失败,并返回EINVAL,我们可以通过pthread_detach来detach一个线程:

#include <pthread.h>  
  
int pthread_detach(pthread_t tid); 

5、线程同步

当多个线程共享相同的内存时,我们需要保证每一个线程都看到一个一致的数据。如果一个线程的变量别的线程不能够读写,或者变量时只读的,那么不会有不一致的状态。

然而一个线程可以修改一个变量,而其他的进程同时可以读取或者修改它,我们需要同步线程来保证它们访问变量时,使用的是一个合法的值。

1)互斥量(Mutexes):

我们可以通过pthread提供的互斥量接口来保护我们的数据,确保每次只有一个线程访问。一个mutex基本上是一个锁,我们在访问共享数据的时候设置(上锁),在访问完成后释放(解锁)。当我们解锁的互斥量的时候,当有多余一个的线程被阻塞时,所有阻塞在这个锁的线程都被唤醒,变成可以运行的状态,只有一个线程开始运行并设置锁,其他的看到互斥量仍然是被锁定,继续等待。

互斥量使用pthread_mutex_t数据类型,在我们使用一个互斥量变量时,我们必须先初始化它,可以初始化为PTHREAD_MUTEX_INITIALIZER(静态初始化)或者调用pthread_mutext_init,如果我们动态申请了互斥量,我们需要调用pthread_mutext_destory来销毁它:

#include <pthread.h>  
  
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t * restrict attr);  
int pthread_mutex_destory(pthread_mutex_t *mutex);  

如果想使用默认的属性来初始化互斥量,我们把attr设置为NULL。

例子:

1)静态初始化

pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;  

2)动态初始化:

int error;  
pthread_mutex_t mylock;  
  
if (error = pthread_mutex_init(&mylock, NULL))  
   fprintf(stderr, "Failed to initialize mylock:%s\n", strerror(error));  

想给一个互斥量上锁,我们调用pthread_mutex_lock.如果mutex已经上锁,调用的线程将会被阻塞,直至信号量解锁。要解锁一个信号量,我们调用phtread_mutex_unlock

int pthread_mutex_lock(pthread_mutex_t *mutex);  
int pthread_mutex_trylock(pthread_mutex_t *mutex);  
int pthread_mutex_unlock(pthread_mutex_t *mutex);  

一个线程如果lock一个已经上锁的互斥量,不想被阻塞,那么可以使用pthread_mutex_trylock,如果调用它的时候没有被上锁,就锁住这个互斥量,如果已经上锁,就会失败,并返回EBUSY。

例子:

我们使用mutex来保护数据结构:当多个进程需要访问动态申请的结构,我们嵌入了引用计数,来保证直到所有线程都使用完它时,我们才释放它。

#include <pthread.h>  
#include <stdlib.h>  
  
struct foo{  
    int f_count;  
    pthread_mutex_t f_lock;  
    /* ...more stuff here... */  
};  
  
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);  
    }  
}  

2)读写锁:

读写锁也叫共享-排他锁,和互斥量类似,除了它可以提供更高的并行性。使用mutex,它的状态要么处于锁住和未锁状态,只有一个线程可以上锁。而读写锁有更多的状态:在读状态锁住,在写状态锁住,未锁住。只有一个线程可以获得写锁,多个线程可以同时获得读锁。当读写锁处于写锁住状态,所有试图上锁的进程都被阻塞,当读写锁处于读锁住状态时,所有试图上读状态的锁成功,但是试图获得写状态锁将会被阻塞,直到所有的读进程都释放读状态锁,此后来到试图上读锁的线程也被阻塞。

读写锁适合读比写频繁情形。读写锁和互斥量一样也需要在使用前初始化,在释放他们内存的时候销毁。

#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 *restrict rwlock);  

一个读写锁可以调用pthread_rwlock_init来初始化,我们可以传递NULL作为attr的参数,这样会使用读写锁的默认属性。我们可以调用pthread_rwlock_destroy来清理,销毁它所占的内存空间。

上锁:

#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);  

实现上可能会对读写锁中读模式的锁锁住次数有一定的限制,所以我们需要检查返回值,以确定是否成功。而其他的两个函数会返回错误,但是只要我们的锁设计的恰当,我们可以不必做检查。Single UNIX规范另外两个读写锁原语:

#include <pthread.h>  
  
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);  
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);  

当锁成功获取时,返回0,否则返回EBUSY。这两个函数使用在一个上锁的结构不能够保证不产生死锁的时候,它可以避免死锁。

3)条件变量:

条件变量是另一中线程同步的机制,允许线程以无竞争的方式等待特定的条件发生。条件变量本身需要互斥量的保护,线程在改变条件前必须首先锁住互斥量,且只有在锁住互斥量以后才能计算条件。条件变量使用之前必须首先进行初始化,pthread_cond_t数据类型代表的条件变量可以用两种方式初始化。

可以把常量PTHREAD_COND_INITIALIZER赋给静态分配的条件变量,但是如果条件变量是动态分配的,可以使用pthread_cond_init函数进行初始化。

在释放底层的内存空间前,可以使用pthread_mutex_destroy函数对条件变量进行销毁。除非需要创建一个非默认属性的条件变量,否则pthread_cond_init函数的attr参数可以设置为NULL。

#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);                       

成功返回0,失败返回错误码。使用pthread_cond_wait等待条件变为真,如果在给定时间内条件不能满足,那么会生成一个代表出错码的返回值。

pthread_cond_wait

调用者需要把锁住的互斥量传给pthread_cond_wait对条件进行保护。函数会把调用线程放到等待条件的线程列表上,然后对互斥量解锁,这两个操作是原子操作。当pthread_cond_wait返回时,互斥量再次被锁住。

先加锁,防止:在检查条件变量为假之后,正准备将线程放入等待队列,这之间条件变量发生了改变,而调用pthread_cond_wait的线程感知不到该变化。所以要加锁,这样别人就无法在这个time window里面改变条件变量。最后解锁自然是为了让别人来改变,返回的时候也加上锁,因为现在是这个线程进入了临界区。

#include <pthread.h>   
  
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);  
int pthread_cond_timewait(pthread_cond_t * restict cond, pthread_mutex_t *restrict mutex,const struct timespec * restrict timeout);  

pthread_cond_timedwait函数的工作方式与pthread_cond_wait函数相似。timeout值指定了等待的时间,它通过timespec结构指定。时间值用秒数或者分秒数表示,分秒数的单位是纳秒。时间值是一个绝对数而不是相对数。可以使用gettimeofday获取用timeval结构表示的当前时间,然后把这个时间加上要等待的时间转换成timespec结构:

void maketimeout(struct timespec *tsp, long minutes){  
    struct timeval now;  
    /* get the current time */  
    gettimeofday(&now);  
    tsp->tv_sec = now.tv_sec;  
    tsp->tv_nsec = now.tv_usec * 10000; /* usec to nsec */  
    tsp->tv_sec += minutes * 60;  
}  

如果时间值到了但是条件还没有出现,pthread_cond_timedwait将重新获取互斥量,然后返回错误ETIMEDOUT。从pthread_cond_wait或者pthread_cond_timedwait调用成功返回时,线程需要重新计算条件,因为其它线程可能已经在运行并改变了条件。

这里也是时间窗口的问题,因为唤醒后投入运行之间有一段间隙。

pthread_cond_signal函数将唤醒等待该条件的某个线程,而pthread_cond_broadcast函数将唤醒等待该条件的所有线程。必须注意一定要在改变条件状态以后再唤醒等待线程

#include <pthread.h>  
  
int pthread_cond_signal(pthread_cond_t *cond);  
int pthread_cond_broadcast(pthread_cond_t *cond);   

例子:

#include <pthread.h>  
  
struct msg {  
    struct msg *m_next;  
    /* ... more stuff here ... */  
};  
  
struct msg *workq;  
pthread_cond_t qready = PTHREAD_COND_INITIALIZER;   /*初始化条件变量*/  
pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;  /*初始化互斥量*/  
  
void process_msg(void)  
{  
    struct msg *mp;   
  
    for (;;) {  
        pthread_mutex_lock(&qlock);     /*条件本身由互斥量保护*/  
        while (workq == NULL)           /*wait返回后要重新检查条件*/  
            pthread_cond_wait(&qready, &qlock);  /*wait期间释放互斥量,返回时再次锁住*/  
        mp = workq;  
        workq = mp->m_next;  
        pthread_mutex_unlock(&qlock);   /*真正释放互斥量*/  
        /* now process the message mp */  
    }  
}  
void enqueue_msg(struct msg *mp)  
{  
    pthread_mutex_lock(&qlock);       /*修改条件前锁住互斥量*/  
    mp->m_next = workq;  
    workq = mp;  
    pthread_mutex_unlock(&qlock);  
    pthread_cond_signal(&qready);     /*唤醒等待线程时不需要占有互斥量*/  
  
                                     /*如果希望在wait返回时不用再检查条件,就需要在唤醒时占有互斥量*/  
}  

参考:《Advanced programming in Unix Environment 2ed》第11章

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值