首页 > 编程语言 > 详细

笔记整理--Linux多线程

时间:2017-01-16 17:56:19      阅读:231      评论:0      收藏:0      [点我收藏+]

Unix高级环境编程系列笔记 (2013/11/17 14:26:38)

Unix高级环境编程系列笔记

   通过这篇文字,您将能够解答如下问题:

  • 如何来标识一个线程?

  • 如何创建一个新线程?

  • 如何实现单个线程的退出?

  • 如何使调用线程阻塞等待指定线程的退出,并获得退出线程的返回码?

  • 如何通过一个线程让另外一个线程退出?

  • 如何实现线程退出时的清理动作?

  • Unix系统如何实现线程之间的同步?

  • 什么情况会发生线程死锁,如何避免死锁?

  • 读写锁的使用方法。

  • 什么是条件变量,它有什么作用?

  • 如何使用条件变量?

  • 1. 如何来标识一个线程?

       表示进程号的为pid_t类型,表示线程号的是pthread_t类型。pthread_t是一个结构体而不是整型。

       使用pthread_equal确定两个线程号是否相等:

    #include 

       使用pthread_self函数来获取线程的ID:

    #include 

    2.如何创建一个新线程?

       使用pthread_create函数创建一个新线程。


    以下是代码片段:
    #include  
    int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void), void *restrict arg);
    Returns: 0 if OK, error number on failure


       当该函数成功返回的时候,tidp所指向的内存位置将被分配给新创建的带有thread ID的线程。

       attr用来定制各种线程参数。

       新创建的线程将在start_rtn函数所指向的地址开始运行,该函数接受一个参数无类型的指针arg作为参数

       线程创建时无法保证哪个线程会先运行。新创建的线程可以访问进程地址空间,并且继承了调用线程的浮点环境以及信号量掩码,但对于线程的未决信号量也将会被清除。

       下面的这段程序创建新的线程,并打印线程id,新线程通过pthread_self函数获取自己的线程ID。

    #include "apue.h"
    #include 

    3. 如何实现单个线程的退出?

       如果一个线程调用了exit, _Exit, 或者_exit,将导致整个进程的终止。要实现单个线程的退出,可以采用如下方式:

       o 线程可以简单的从start routine返回,返回值就是线程的退出代码。

       o 线程可以被同一进程中的其它线程终止。

       o 线程调用pthread_exit

    #include 

    4.如何使调用线程阻塞等待指定线程的退出,并获得退出线程的返回码?

    #include 

       调用线程将会被阻塞直到指定的线程终止。如果线程简单的从start routine返回则rval_ptr将包含返回代码。如果线程是被撤销(调用pthread_exit)的,rval_ptr指向的内存地址将被设置为PTHREAD_CANCELED.

       通过调用pthread_join,我们自动的将一个线程变成分离状态,这样就可以实现资源的回收。如果线程已经处于分离状态,调用pthread_join将会失败,并返回EINVAL。

       如果我们对于线程的返回值不感兴趣,可以将rval_ptr设置成NULL。

       一段有缺陷的代码:

    #include "apue.h"
    #include 

       注意,pthread_create 和 pthread_exit函数的无类型指针可以传递复杂的结构信息,但这个结构所使用的内存在调用者完成后必须仍然有效(分配在堆上或者是静态变量),否则就会出现使用无效的错误。这段代码中thr_fn1函数中变量foo分配在栈上,但该线程退出后,主线程通过pthread_join获取foo的地址并进行操作(调用printfoo函数时)就会出现错误,因为此时thr_fn1已经退出它的栈已经被销毁。

    5.如何通过一个线程让另外一个线程退出?

       调用pthread_cancel函数将导致tid所指向的线程终止运行。但是,一个线程可以选择忽略其它线程控制该线程何时退出。注意,该函数并不等待线程终止,它仅仅提出要求。

    #include 

    6.如何实现线程退出时的清理动作?

       线程可以建立多个清理处理程序,这些程序记录在栈中,也就是说他们的执行顺序与注册顺序想法。使用如下函数注册清理函数:

       void pthread_cleanup_push(void (*rtn)(void *), void *arg);

       void pthread_cleanup_pop(int execute);

       rtn将被调用,并传以arg参数,引起该函数调用的情况如下:

       o 调用pthread_exit

       o 对于退出请求的反应

       o 以非0参数调用pthread_cleanup_push

       如果pthread_cleanup_pop的参数非0则仅仅移除该处理函数而不执行。

       如果函数已经处于分离状态,则当它退出时线程底层的存储资源会被立即回收。处于分离状态的线程,如果调用pthread_join来等待其退出将会出现错误。

       通过下列函数可以让进程处于分离状态:

    #include 

    7.Unix系统如何实现线程之间的同步?

       使用pthreads mutual-exclusion interfaces。引入了mutex,用pthread_mutex_t类型来表示。在使用这个变量之前,我们首先要将其初始化,或者赋值为PTHREAD_MUTEX_INITIALIZER(仅仅用于静态分配的mutexs),或者调用pthread_mutex_init。如果我们动态的为mutex分配空间(例如通过调用malloc),我们需要在调用free释放内存之前调用pthread_mutex_destroy。

       函数定义如下:


    以下是代码片段:
    #include  
    int pthread_mutex_init(pthread_mutex_t *restrict mutex,  const pthread_mutexattr_t *restrict attr);
    int pthread_mutex_destroy(pthread_mutex_t *mutex);
    Both return: 0 if OK, error number on failure


       初始化mutex时参数attr用来指定mutex的属性,要使用默认值将它设置为NULL。

       使用如下函数对mutex进行加锁或解锁:

    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
    

       注意当mutex已经被加锁则 pthread_mutex_lock会阻塞。如果一个线程无法忍受阻塞,可以调用pthread_mutex_trylock来加锁,加锁失败则立即返回EBUSY。

    8.什么情况会发生线程死锁,如何避免死锁?

       如果一个线程对mutex加两次锁则显然会导致死锁。但实际上死锁的情况要复杂的多:when we use more than one mutex in our programs, a deadlock can occur if we allow one thread to hold a mutex and block while trying to lock a second mutex at the same time that another thread holding the second mutex tries to lock the first mutex. Neither thread can proceed, because each needs a resource that is held by the other, so we have a deadlock.

       死锁可以通过控制加锁的顺序来避免。有两个mutex A和B,如果所有的线程总是先对A加锁再对B加锁就不会产生死锁。但实际应用中可能很难保证这种顺序加锁的方式,这种情况下,可以使用pthread_mutex_trylock来避免死锁的发生。

    9.读写锁的使用方法。

       读写锁的初始化与销毁:


    以下是代码片段:
    #include  
    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
   对于读写锁的初始化与销毁独占锁类似。


   加锁与解锁:

#include 

   对于读者的数量会有限制,因此调用 pthread_rwlock_rdlock时需要检查返回值。

   在正确使用的情况下,不需要检查pthread_rwlock_wrlock和pthread_rwlock_unlock的返回值。

   条件加锁:

#include 

10.什么是条件变量,它有什么作用?

   条件变量是线程可用的另外一种同步机制。条件变量给多个线程提供了一个会合的场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定条件的发生。条件本身是由互斥量保护的。线程在改变状态前必须首先锁住互斥量,其它线程在获得互斥量之前不会觉察到这种变化。

11.如何使用条件变量?

   条件变量的类型为pthread_cond_t ,其初始化与销毁的方式与mutex类似,注意静态变量可以通过指定常量PTHREAD_COND_INITIALIZER来进行初始化。

#include 

   使用pthread_cond_wait来等待条件变成真。

   函数定义如下:


以下是代码片段:
#include  
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond,  pthread_mutex_t *restrict mutex, const struct timespec *restrict timeout);

Both return: 0 if OK, error number on failure
   调用者把锁住的mutex传递给pthread_cond_wait,函数把调用线程放到等待条件变量的线程列表上,然后对互斥量解锁,这两个操作是原子操作。这样就关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间窗口。pthread_cond_wait返回时,mutex会再次被锁住。


   注意,调用成功返回后,线程需要重新计算条件变量,因为其它线程可能已经改变了条件。

   有两个函数用于通知线程一个条件已经被满足。pthread_cond_signal函数用来唤醒一个等待条件满足的线程, pthread_cond_broadcast用来唤醒所有等待条件满足的线程。

   他们的定义为:

#include 

   下面的这段代码实现了类似于生产者消费者模型的程序,生产者通过enqueue_msg将消息放入队列,并发送信号通知给消费者线程。消费者线程被唤醒然后处理消息。

#include 

   在pthread_cond_signal发送消息之前并不需要占用锁,因为一旦线程被唤醒后通过while发现没有要处理的msg存在则会再次陷入睡眠。如果系统不能容忍这种竞争环境,则需要在unlock之前调用cond_signal,但是在多处理器机器上,这样会导致多线程被唤醒然后立即进入阻塞(cond_signal唤醒线程,但由于我们仍占用着锁,所以这些线程又会立即阻塞)。


Linux多线程编程详细解析----条件变量 pthread_cond_t - IT-Homer - 博客频道 - CSDN.NET - Google Chrome (2013/9/5 22:01:51)

Linux多线程编程详细解析----条件变量 pthread_cond_t

分类: Linux/Shell 9642人阅读 评论(0) 收藏 举报

Linux操作系统下的多线程编程详细解析----条件变量

 

1.初始化条件变量pthread_cond_init

#include <pthread.h>

int pthread_cond_init(pthread_cond_t *cv,

const pthread_condattr_t *cattr);

返回值:函数成功返回0;任何其他返回值都表示错误

初始化一个条件变量。当参数cattr为空指针时,函数创建的是一个缺省的条件变量。否则条件变量的属性将由cattr中的属性值来决定。调用 pthread_cond_init函数时,参数cattr为空指针等价于cattr中的属性为缺省属性,只是前者不需要cattr所占用的内存开销。这个函数返回时,条件变量被存放在参数cv指向的内存中。

可以用宏PTHREAD_COND_INITIALIZER来初始化静态定义的条件变量,使其具有缺省属性。这和用pthread_cond_init函数动态分配的效果是一样的。初始化时不进行错误检查。如:

pthread_cond_t cv = PTHREAD_COND_INITIALIZER;

不能由多个线程同时初始化一个条件变量。当需要重新初始化或释放一个条件变量时,应用程序必须保证这个条件变量未被使用。

 

2.阻塞在条件变量上pthread_cond_wait

#include <pthread.h>

int pthread_cond_wait(pthread_cond_t *cv,

pthread_mutex_t *mutex);

返回值:函数成功返回0;任何其他返回值都表示错误

函数将解锁mutex参数指向的互斥锁,并使当前线程阻塞在cv参数指向的条件变量上。

被阻塞的线程可以被pthread_cond_signal函数,pthread_cond_broadcast函数唤醒,也可能在被信号中断后被唤醒。

pthread_cond_wait函数的返回并不意味着条件的值一定发生了变化,必须重新检查条件的值。

pthread_cond_wait函数返回时,相应的互斥锁将被当前线程锁定,即使是函数出错返回。

一般一个条件表达式都是在一个互斥锁的保护下被检查。当条件表达式未被满足时,线程将仍然阻塞在这个条件变量上。当另一个线程改变了条件的值并向条件变量发出信号时,等待在这个条件变量上的一个线程或所有线程被唤醒,接着都试图再次占有相应的互斥锁。

阻塞在条件变量上的线程被唤醒以后,直到pthread_cond_wait()函数返回之前条件的值都有可能发生变化。所以函数返回以后,在锁定相应的互斥锁之前,必须重新测试条件值。最好的测试方法是循环调用pthread_cond_wait函数,并把满足条件的表达式置为循环的终止条件。如:

pthread_mutex_lock();

while (condition_is_false)

pthread_cond_wait();

pthread_mutex_unlock();

阻塞在同一个条件变量上的不同线程被释放的次序是不一定的。

注意:pthread_cond_wait()函数是退出点,如果在调用这个函数时,已有一个挂起的退出请求,且线程允许退出,这个线程将被终止并开始执行善后处理函数,而这时和条件变量相关的互斥锁仍将处在锁定状态。

 

3.解除在条件变量上的阻塞pthread_cond_signal

#include <pthread.h>

int pthread_cond_signal(pthread_cond_t *cv);

返回值:函数成功返回0;任何其他返回值都表示错误

函数被用来释放被阻塞在指定条件变量上的一个线程。

必须在互斥锁的保护下使用相应的条件变量。否则对条件变量的解锁有可能发生在锁定条件变量之前,从而造成死锁。

唤醒阻塞在条件变量上的所有线程的顺序由调度策略决定,如果线程的调度策略是SCHED_OTHER类型的,系统将根据线程的优先级唤醒线程。

如果没有线程被阻塞在条件变量上,那么调用pthread_cond_signal()将没有作用。

 

4.阻塞直到指定时间pthread_cond_timedwait

#include <pthread.h>

#include <time.h>

int pthread_cond_timedwait(pthread_cond_t *cv,

pthread_mutex_t *mp, const structtimespec * abstime);

返回值:函数成功返回0;任何其他返回值都表示错误

函数到了一定的时间,即使条件未发生也会解除阻塞。这个时间由参数abstime指定。函数返回时,相应的互斥锁往往是锁定的,即使是函数出错返回。

注意:pthread_cond_timedwait函数也是退出点。

超时时间参数是指一天中的某个时刻。使用举例:

pthread_timestruc_t to;

to.tv_sec = time(NULL) + TIMEOUT;

to.tv_nsec = 0;

超时返回的错误码是ETIMEDOUT。

 

5.释放阻塞的所有线程pthread_cond_broadcast

#include <pthread.h>

int pthread_cond_broadcast(pthread_cond_t *cv);

返回值:函数成功返回0;任何其他返回值都表示错误

函数唤醒所有被pthread_cond_wait函数阻塞在某个条件变量上的线程,参数cv被用来指定这个条件变量。当没有线程阻塞在这个条件变量上时,pthread_cond_broadcast函数无效。

由于pthread_cond_broadcast函数唤醒所有阻塞在某个条件变量上的线程,这些线程被唤醒后将再次竞争相应的互斥锁,所以必须小心使用pthread_cond_broadcast函数。

 

6.释放条件变量pthread_cond_destroy

#include <pthread.h>

int pthread_cond_destroy(pthread_cond_t *cv);

返回值:函数成功返回0;任何其他返回值都表示错误

释放条件变量。

注意:条件变量占用的空间并未被释放。

 

7.唤醒丢失问题

在线程未获得相应的互斥锁时调用pthread_cond_signal或pthread_cond_broadcast函数可能会引起唤醒丢失问题。

唤醒丢失往往会在下面的情况下发生:

  1. 一个线程调用pthread_cond_signal或pthread_cond_broadcast函数;
  2. 另一个线程正处在测试条件变量和调用pthread_cond_wait函数之间;
  3. 没有线程正在处在阻塞等待的状态下。

 

转载声明: 本文转自 http://pzs1237.blog.163.com/blog/static/29813006200952335454934/

 

===============================================================================

 

条件锁pthread_cond_t

 

说明,
等待线程
1。使用pthread_cond_wait前要先加锁
2。pthread_cond_wait内部会解锁,然后等待条件变量被其它线程激活
3。pthread_cond_wait被激活后会再自动加锁

激活线程:
1。加锁(和等待线程用同一个锁)
2。pthread_cond_signal发送信号
3。解锁
激活线程的上面三个操作在运行时间上都在等待线程的pthread_cond_wait函数内部。

程序示例:

  1. #include <stdio.h>  
  2. #include <pthread.h>  
  3. #include <unistd.h>  
  4.   
  5. pthread_mutex_t count_lock;  
  6. pthread_cond_t count_nonzero;  
  7. unsigned count = 0;  
  8.   
  9. void *decrement_count(void *arg)  
  10. {  
  11.     pthread_mutex_lock(&count_lock);  
  12.     printf("decrement_count get count_lock/n");  
  13.     while(count == 0)  
  14.     {  
  15.         printf("decrement_count count == 0 /n");  
  16.         printf("decrement_count before cond_wait /n");  
  17.         pthread_cond_wait(&count_nonzero, &count_lock);  
  18.         printf("decrement_count after cond_wait /n");  
  19.     }  
  20.   
  21.     count = count + 1;  
  22.     pthread_mutex_unlock(&count_lock);  
  23. }  
  24.   
  25. void *increment_count(void *arg)  
  26. {  
  27.     pthread_mutex_lock(&count_lock);  
  28.     printf("increment_count get count_lock /n");  
  29.     if(count == 0)  
  30.     {  
  31.         printf("increment_count before cond_signal /n");  
  32.         pthread_cond_signal(&count_nonzero);  
  33.         printf("increment_count after cond_signal /n");  
  34.     }  
  35.   
  36.     count = count + 1;  
  37.     pthread_mutex_unlock(&count_lock);  
  38. }  
  39.   
  40. int main(void)  
  41. {  
  42.     pthread_t tid1, tid2;  
  43.   
  44.     pthread_mutex_init(&count_lock, NULL);  
  45.     pthread_cond_init(&count_nonzero, NULL);  
  46.   
  47.     pthread_create(&tid1, NULL, decrement_count, NULL);  
  48.     sleep(2);  
  49.     pthread_create(&tid2, NULL, increment_count, NULL);  
  50.   
  51.     sleep(10);  
  52.     pthread_exit(0);  
  53.   
  54.     return 0;  
  55. }  


运行结果:

[work@db-testing-com06-vm3.db01.baidu.com pthread]$ gcc -o pthread_cond pthread_cond.c -lpthread
[work@db-testing-com06-vm3.db01.baidu.com pthread]$ ./pthread_cond 
decrement_count get count_lock
decrement_count count == 0 
decrement_count before cond_wait 
increment_count get count_lock 
increment_count before cond_signal 
increment_count after cond_signal 
decrement_count after cond_wait

 

转载声明: 本文转自 http://egeho123.blogbus.com/logs/10821816.html

 

===============================================================================

 

多线程编程,条件变量pthread_cond_t应用

 

程序代码:

 

  1. #include <stdio.h>  
  2. #include <pthread.h>  
  3. #include <unistd.h>  
  4.   
  5. pthread_mutex_t counter_lock;  
  6. pthread_cond_t counter_nonzero;  
  7. int counter = 0;  
  8. int estatus = -1;  
  9.   
  10. void *decrement_counter(void *argv);  
  11. void *increment_counter(void *argv);  
  12.   
  13. int main(int argc, char **argv)  
  14. {  
  15.     printf("counter: %d/n", counter);  
  16.     pthread_t thd1, thd2;  
  17.     int ret;  
  18.   
  19.     ret = pthread_create(&thd1, NULL, decrement_counter, NULL);  
  20.     if(ret){  
  21.         perror("del:/n");  
  22.         return 1;  
  23.     }  
  24.   
  25.     ret = pthread_create(&thd2, NULL, increment_counter, NULL);  
  26.     if(ret){  
  27.         perror("inc: /n");  
  28.         return 1;  
  29.     }  
  30.   
  31.     int counter = 0;  
  32.     while(counter != 10){  
  33.         printf("counter: %d/n", counter);  
  34.         sleep(1);  
  35.         counter++;  
  36.     }  
  37.   
  38.     return 0;  
  39. }  
  40.   
  41. void *decrement_counter(void *argv)  
  42. {  
  43.     pthread_mutex_lock(&counter_lock);  
  44.     while(counter == 0)  
  45.         pthread_cond_wait(&counter_nonzero, &counter_lock);  
  46.   
  47.     counter--;  
  48.     pthread_mutex_unlock(&counter_lock);  
  49.   
  50.     return &estatus;  
  51. }  
  52.   
  53. void *increment_counter(void *argv)  
  54. {  
  55.     pthread_mutex_lock(&counter_lock);  
  56.     if(counter == 0)  
  57.         pthread_cond_signal(&counter_nonzero);  
  58.   
  59.     counter++;  
  60.     pthread_mutex_unlock(&counter_lock);  
  61.   
  62.     return &estatus;  
  63. }  

 

运行结果:
[work@db-testing-com06-vm3.db01.baidu.com pthread]$ gcc -o pthread_cond2 pthread_cond2.c -lpthread
[work@db-testing-com06-vm3.db01.baidu.com pthread]$ ./pthread_cond2                               
counter: 0
counter: 0
counter: 1
counter: 2
counter: 3
counter: 4
counter: 5
counter: 6
counter: 7
counter: 8
counter: 9

 

调试程序的运行过程:

1、开始时 counter 为0 (main)
2、ret = pthread_create(&thrd1, NULL, decrement_counter, NULL)处生成一个thrd1线程运行decrement_counter(),
此线程内函数运行流程为:
先锁定 互斥锁(count_lock) 如果counter为0,此线程被阻塞在条件变量(count_nonzero)上.同时释放互斥锁count_lock(wait内部会先释放锁,等待signal激活后自动再加上锁).

 

3、与此同时主程序还在运行,创建另一个线程thrd2运行 increment_counter,
此线程内的函数流程如下:
先锁定 互斥锁(count_lock)【wait内部释放锁的互斥锁】 如果counter为0, 唤醒在条件变量(count_nonzero)上的线程即thrd1.但是由于有互斥锁count_lock【signal激活后,wait内部又自动加上锁了】, thrd1还是在等待. 然后count++,释放互斥锁,.......thrd1由于互斥锁释放,重新判断counter是不是为0,如果为0再把线程阻塞在条件变量count_nonzero上,但这时counter已经为1了.所以线程继续运行.counter--释放互斥锁......(退出后,运行主线程main
4、与此主程序间隔打印counter运行一段时间退出.

 注:更清晰的运行流程请详见如下“改进代码”

后记,在编译的时候加上 -lpthread
改进代码:

  1. #include <stdio.h>  
  2. #include <pthread.h>  
  3. #include <unistd.h>  
  4.   
  5. pthread_mutex_t counter_lock;  
  6. pthread_cond_t counter_nonzero;  
  7. int counter = 0;  
  8. int estatus = -1;  
  9.   
  10. void *decrement_counter(void *argv);  
  11. void *increment_counter(void *argv);  
  12.   
  13. int main(int argc, char **argv)  
  14. {  
  15.     printf("counter: %d/n", counter);  
  16.     pthread_t thd1, thd2;  
  17.     int ret;  
  18.   
  19.     ret = pthread_create(&thd1, NULL, decrement_counter, NULL);  
  20.     if(ret){  
  21.         perror("del:/n");  
  22.         return 1;  
  23.     }  
  24.   
  25.     ret = pthread_create(&thd2, NULL, increment_counter, NULL);  
  26.     if(ret){  
  27.         perror("inc: /n");  
  28.         return 1;  
  29.     }  
  30.   
  31.     int counter = 0;  
  32.     while(counter != 10){  
  33.         printf("counter(main): %d/n", counter);  
  34.         sleep(1);  
  35.         counter++;  
  36.     }  
  37.   
  38.     return 0;  
  39. }  
  40.   
  41. void *decrement_counter(void *argv)  
  42. {  
  43.     printf("counter(decrement): %d/n", counter);  
  44.     pthread_mutex_lock(&counter_lock);  
  45.     while(counter == 0)  
  46.         pthread_cond_wait(&counter_nonzero, &counter_lock); //进入阻塞(wait),等待激活(signal)  
  47.       
  48.     printf("counter--(before): %d/n", counter);      
  49.     counter--; //等待signal激活后再执行  
  50.     printf("counter--(after): %d/n", counter);      
  51.     pthread_mutex_unlock(&counter_lock);   
  52.   
  53.     return &estatus;  
  54. }  
  55.   
  56. void *increment_counter(void *argv)  
  57. {  
  58.     printf("counter(increment): %d/n", counter);  
  59.     pthread_mutex_lock(&counter_lock);  
  60.     if(counter == 0)  
  61.         pthread_cond_signal(&counter_nonzero); //激活(signal)阻塞(wait)的线程(先执行完signal线程,然后再执行wait线程)  
  62.   
  63.     printf("counter++(before): %d/n", counter);      
  64.     counter++;   
  65.     printf("counter++(after): %d/n", counter);      
  66.     pthread_mutex_unlock(&counter_lock);  
  67.   
  68.     return &estatus;  
  69. }  

运行结果:
[work@db-testing-com06-vm3.db01.baidu.com pthread]$ gcc -o pthread_cond2 pthread_cond2.c -lpthread
[work@db-testing-com06-vm3.db01.baidu.com pthread]$ ./pthread_cond2                               
counter: 0
counter(main): 0
counter(decrement): 0
counter(increment): 0
counter++(before): 0
counter++(after): 1
counter--(before): 1
counter--(after): 0
counter(main): 1
counter(main): 2
counter(main): 3
counter(main): 4
counter(main): 5
counter(main): 6
counter(main): 7
counter(main): 8
counter(main): 9

多线程--条件变量 - DZQABC - 博客园 - Google Chrome (2013/9/5 22:00:10)

条件变量函数

 

操作

相关函数说明

初始化条件变量

pthread_cond_init 语法

基于条件变量阻塞

pthread_cond_wait 语法

解除阻塞特定线程

pthread_cond_signal 语法

在指定的时间之前阻塞

pthread_cond_timedwait 语法

在指定的时间间隔内阻塞

pthread_cond_reltimedwait_np 语法

解除阻塞所有线程

pthread_cond_broadcast 语法

销毁条件变量状态

pthread_cond_destroy 语法

初始化条件变量

使用 pthread_cond_init(3C) 可以将 cv 所指示的条件变量初始化为其缺省值,或者指定已经使用 pthread_condattr_init() 设置的条件变量属性。

pthread_cond_init 语法

int pthread_cond_init(pthread_cond_t *cv,

const pthread_condattr_t *cattr);
#include <pthread.h>



pthread_cond_t cv;

pthread_condattr_t cattr;

int ret;



/* initialize a condition variable to its default value */

ret = pthread_cond_init(&cv, NULL);



/* initialize a condition variable */

ret = pthread_cond_init(&cv, &cattr);

cattr 设置为 NULL。将 cattr 设置为 NULL 与传递缺省条件变量属性对象的地址等效,但是没有内存开销。对于 Solaris 线程,请参见cond_init 语法。

使用 PTHREAD_COND_INITIALIZER 宏可以将以静态方式定义的条件变量初始化为其缺省属性。PTHREAD_COND_INITIALIZER 宏与动态分配具有 null 属性的 pthread_cond_init() 等效,但是不进行错误检查。

多个线程决不能同时初始化或重新初始化同一个条件变量。如果要重新初始化或销毁某个条件变量,则应用程序必须确保该条件变量未被使用。

pthread_cond_init 返回值

pthread_cond_init() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。

EINVAL

描述:

cattr 指定的值无效。

EBUSY

描述:

条件变量处于使用状态。

EAGAIN

描述:

必要的资源不可用。

ENOMEM

描述:

内存不足,无法初始化条件变量。

基于条件变量阻塞

使用 pthread_cond_wait(3C) 可以以原子方式释放 mp 所指向的互斥锁,并导致调用线程基于 cv 所指向的条件变量阻塞。对于 Solaris 线程,请参见cond_wait 语法。

pthread_cond_wait 语法

int pthread_cond_wait(pthread_cond_t *cv,pthread_mutex_t *mutex);
#include <pthread.h>



pthread_cond_t cv;

pthread_mutex_t mp;

int ret;



/* wait on condition variable */

ret = pthread_cond_wait(&cv, &mp);

阻塞的线程可以通过 pthread_cond_signal()  pthread_cond_broadcast() 唤醒,也可以在信号传送将其中断时唤醒。

不能通过 pthread_cond_wait() 的返回值来推断与条件变量相关联的条件的值的任何变化。必须重新评估此类条件。

pthread_cond_wait() 例程每次返回结果时调用线程都会锁定并且拥有互斥锁,即使返回错误时也是如此。

该条件获得信号之前,该函数一直被阻塞。该函数会在被阻塞之前以原子方式释放相关的互斥锁,并在返回之前以原子方式再次获取该互斥锁。

通常,对条件表达式的评估是在互斥锁的保护下进行的。如果条件表达式为假,线程会基于条件变量阻塞。然后,当该线程更改条件值时,另一个线程会针对条件变量发出信号。这种变化会导致所有等待该条件的线程解除阻塞并尝试再次获取互斥锁。

必须重新测试导致等待的条件,然后才能从 pthread_cond_wait() 处继续执行。唤醒的线程重新获取互斥锁并从 pthread_cond_wait() 返回之前,条件可能会发生变化。等待线程可能并未真正唤醒。建议使用的测试方法是,将条件检查编写为调用 pthread_cond_wait()  while() 循环。

pthread_mutex_lock();

while(condition_is_false)

pthread_cond_wait();

pthread_mutex_unlock();

如果有多个线程基于该条件变量阻塞,则无法保证按特定的顺序获取互斥锁。


注 –

pthread_cond_wait() 是取消点。如果取消处于暂挂状态,并且调用线程启用了取消功能,则该线程会终止,并在继续持有该锁的情况下开始执行清除处理程序。


pthread_cond_wait 返回值

pthread_cond_wait() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。

EINVAL

描述:

cv  mp 指定的值无效。

解除阻塞一个线程

对于基于 cv 所指向的条件变量阻塞的线程,使用 pthread_cond_signal(3C) 可以解除阻塞该线程。对于 Solaris 线程,请参见cond_signal 语法。

pthread_cond_signal 语法

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



pthread_cond_t cv;

int ret;



/* one condition variable is signaled */

ret = pthread_cond_signal(&cv);

应在互斥锁的保护下修改相关条件,该互斥锁用于获得信号的条件变量中。否则,可能在条件变量的测试和 pthread_cond_wait() 阻塞之间修改该变量,这会导致无限期等待。

调度策略可确定唤醒阻塞线程的顺序。对于 SCHED_OTHER,将按优先级顺序唤醒线程。

如果没有任何线程基于条件变量阻塞,则调用 pthread_cond_signal() 不起作用。


示例 4–8 使用 pthread_cond_wait()  pthread_cond_signal()

 

pthread_mutex_t count_lock;

pthread_cond_t count_nonzero;

unsigned count;



decrement_count()

{

pthread_mutex_lock(&count_lock);

while (count == 0)

pthread_cond_wait(&count_nonzero, &count_lock);

count = count - 1;

pthread_mutex_unlock(&count_lock);

}



increment_count()

{

pthread_mutex_lock(&count_lock);

if (count == 0)

pthread_cond_signal(&count_nonzero);

count = count + 1;

pthread_mutex_unlock(&count_lock);

}

pthread_cond_signal 返回值

pthread_cond_signal() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。

EINVAL

描述:

cv 指向的地址非法。

说明了如何使用 pthread_cond_wait()  pthread_cond_signal()

在指定的时间之前阻塞

pthread_cond_timedwait(3C) 的用法与 pthread_cond_wait() 的用法基本相同,区别在于在由 abstime 指定的时间之后 pthread_cond_timedwait() 不再被阻塞。

pthread_cond_timedwait 语法

int pthread_cond_timedwait(pthread_cond_t *cv,

pthread_mutex_t *mp, const struct timespec *abstime);
#include <pthread.h>

#include <time.h>



pthread_cond_t cv;

pthread_mutex_t mp;

timestruct_t abstime;

int ret;



/* wait on condition variable */

ret = pthread_cond_timedwait(&cv, &mp, &abstime);

pthread_cond_timewait() 每次返回时调用线程都会锁定并且拥有互斥锁,即使 pthread_cond_timedwait() 返回错误时也是如此。 对于 Solaris 线程

pthread_cond_timedwait() 函数会一直阻塞,直到该条件获得信号,或者最后一个参数所指定的时间已过为止。


注 –

pthread_cond_timedwait() 也是取消点。



示例 4–9 计时条件等待

 

pthread_timestruc_t to;

pthread_mutex_t m;

pthread_cond_t c;

...

pthread_mutex_lock(&m);

to.tv_sec = time(NULL) + TIMEOUT;

to.tv_nsec = 0;

while (cond == FALSE) {

err = pthread_cond_timedwait(&c, &m, &to);

if (err == ETIMEDOUT) {

/* timeout, do something */

break;

}

}

pthread_mutex_unlock(&m);

pthread_cond_timedwait 返回值

pthread_cond_timedwait() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。

EINVAL

描述:

cv  abstime 指向的地址非法。

ETIMEDOUT

描述:

abstime 指定的时间已过。

超时会指定为当天时间,以便在不重新计算值的情况下高效地重新测试条件,如示例 4–9 中所示。

在指定的时间间隔内阻塞

pthread_cond_reltimedwait_np(3C) 的用法与 pthread_cond_timedwait() 的用法基本相同,唯一的区别在于 pthread_cond_reltimedwait_np() 会采用相对时间间隔而不是将来的绝对时间作为其最后一个参数的值。

pthread_cond_reltimedwait_np 语法

int  pthread_cond_reltimedwait_np(pthread_cond_t *cv, 

pthread_mutex_t *mp,

const struct timespec *reltime);
#include <pthread.h>

#include <time.h>



pthread_cond_t cv;

pthread_mutex_t mp;

timestruct_t reltime;

int ret;



/* wait on condition variable */

ret = pthread_cond_reltimedwait_np(&cv, &mp, &reltime);

pthread_cond_reltimedwait_np() 每次返回时调用线程都会锁定并且拥有互斥锁,即使 pthread_cond_reltimedwait_np() 返回错误时也是如此。对于 Solaris 线程,请参见 cond_reltimedwait(3C)pthread_cond_reltimedwait_np() 函数会一直阻塞,直到该条件获得信号,或者最后一个参数指定的时间间隔已过为止。


注 –

pthread_cond_reltimedwait_np() 也是取消点。


pthread_cond_reltimedwait_np 返回值

pthread_cond_reltimedwait_np() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。

EINVAL

描述:

cv  reltime 指示的地址非法。

ETIMEDOUT

描述:

reltime 指定的时间间隔已过。

解除阻塞所有线程

对于基于 cv 所指向的条件变量阻塞的线程,使用 pthread_cond_broadcast(3C) 可以解除阻塞所有这些线程,这由 pthread_cond_wait() 来指定。

pthread_cond_broadcast 语法

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



pthread_cond_t cv;

int ret;



/* all condition variables are signaled */

ret = pthread_cond_broadcast(&cv);

如果没有任何线程基于该条件变量阻塞,则调用 pthread_cond_broadcast() 不起作用。对于 Solaris 线程,请参见cond_broadcast 语法。

由于 pthread_cond_broadcast() 会导致所有基于该条件阻塞的线程再次争用互斥锁,因此请谨慎使用 pthread_cond_broadcast()。例如,通过使用pthread_cond_broadcast(),线程可在资源释放后争用不同的资源量,如示例 4–10 中所示。


示例 4–10 条件变量广播

 

pthread_mutex_t rsrc_lock;

pthread_cond_t rsrc_add;

unsigned int resources;



get_resources(int amount)

{

pthread_mutex_lock(&rsrc_lock);

while (resources < amount) {

pthread_cond_wait(&rsrc_add, &rsrc_lock);

}

resources -= amount;

pthread_mutex_unlock(&rsrc_lock);

}



add_resources(int amount)

{

pthread_mutex_lock(&rsrc_lock);

resources += amount;

pthread_cond_broadcast(&rsrc_add);

pthread_mutex_unlock(&rsrc_lock);

}

请注意,在 add_resources() 中,首先更新 resources 还是首先在互斥锁中调用 pthread_cond_broadcast() 无关紧要。

应在互斥锁的保护下修改相关条件,该互斥锁用于获得信号的条件变量中。否则,可能在条件变量的测试和 pthread_cond_wait() 阻塞之间修改该变量,这会导致无限期等待。

pthread_cond_broadcast 返回值

pthread_cond_broadcast() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。

EINVAL

描述:

cv 指示的地址非法。

销毁条件变量状态

使用 pthread_cond_destroy(3C) 可以销毁与 cv 所指向的条件变量相关联的任何状态。对于 Solaris 线程,请参见cond_destroy 语法。

pthread_cond_destroy 语法

int pthread_cond_destroy(pthread_cond_t *cv);
#include <pthread.h>



pthread_cond_t cv;

int ret;



/* Condition variable is destroyed */

ret = pthread_cond_destroy(&cv);

请注意,没有释放用来存储条件变量的空间。

pthread_cond_destroy 返回值

pthread_cond_destroy() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。

EINVAL

描述:

cv 指定的值无效。

注意:pthread_cond_destroy 销毁一个条件变量,释放它拥有的资源。进入 pthread_cond_destroy 之前,必须没有在该条件变量上等待的线程。
Attempting to destroy a condition variable upon which other threads are currently blocked results in undefined behavior.

唤醒丢失问题

如果线程未持有与条件相关联的互斥锁,则调用 pthread_cond_signal()  pthread_cond_broadcast() 会产生唤醒丢失错误。

满足以下所有条件时,即会出现唤醒丢失问题:

  • 一个线程调用 pthread_cond_signal()  pthread_cond_broadcast()

  • 另一个线程已经测试了该条件,但是尚未调用 pthread_cond_wait()

  • 没有正在等待的线程

    信号不起作用,因此将会丢失

仅当修改所测试的条件但未持有与之相关联的互斥锁时,才会出现此问题。只要仅在持有关联的互斥锁同时修改所测试的条件,即可调用 pthread_cond_signal() pthread_cond_broadcast(),而无论这些函数是否持有关联的互斥锁。

生成方和使用者问题

并发编程中收集了许多标准的众所周知的问题,生成方和使用者问题只是其中的一个问题。此问题涉及到一个大小限定的缓冲区和两类线程(生成方和使用者),生成方将项放入缓冲区中,然后使用者从缓冲区中取走项。

生成方必须在缓冲区中有可用空间之后才能向其中放置内容。使用者必须在生成方向缓冲区中写入之后才能从中提取内容。

条件变量表示一个等待某个条件获得信号的线程队列。

示例 4–11 中包含两个此类队列。一个队列 (less) 针对生成方,用于等待缓冲区中出现空位置。另一个队列 (more) 针对使用者,用于等待从缓冲槽位的空位置中提取其中包含的信息。该示例中还包含一个互斥锁,因为描述该缓冲区的数据结构一次只能由一个线程访问。


示例 4–11 生成方和使用者的条件变量问题

 

typedef struct {

char buf[BSIZE];

int occupied;

int nextin;

int nextout;

pthread_mutex_t mutex;

pthread_cond_t more;

pthread_cond_t less;

} buffer_t;



buffer_t buffer;

如示例 4–12 中所示,生成方线程获取该互斥锁以保护 buffer 数据结构,然后,缓冲区确定是否有空间可用于存放所生成的项。如果没有可用空间,生成方线程会调用pthread_cond_wait()pthread_cond_wait() 会导致生成方线程连接正在等待 less 条件获得信号的线程队列。less 表示缓冲区中的可用空间。

与此同时,在调用 pthread_cond_wait() 的过程中,该线程会释放互斥锁的锁定。正在等待的生成方线程依赖于使用者线程在条件为真时发出信号,如示例 4–12 中所示。该条件获得信号时,将会唤醒等待 less 的第一个线程。但是,该线程必须再次锁定互斥锁,然后才能从 pthread_cond_wait() 返回。

获取互斥锁可确保该线程再次以独占方式访问缓冲区的数据结构。该线程随后必须检查缓冲区中是否确实存在可用空间。如果空间可用,该线程会向下一个可用的空位置中进行写入。

与此同时,使用者线程可能正在等待项出现在缓冲区中。这些线程正在等待条件变量 more。刚在缓冲区中存储内容的生成方线程会调用 pthread_cond_signal() 以唤醒下一个正在等待的使用者。如果没有正在等待的使用者,此调用将不起作用。

最后,生成方线程会解除锁定互斥锁,从而允许其他线程处理缓冲区的数据结构。


示例 4–12 生成方和使用者问题:生成方

 

void producer(buffer_t *b, char item)

{

pthread_mutex_lock(&b->mutex);



while (b->occupied >= BSIZE)

pthread_cond_wait(&b->less, &b->mutex);



assert(b->occupied < BSIZE);



b->buf[b->nextin++] = item;



b->nextin %= BSIZE;

b->occupied++;



/* now: either b->occupied < BSIZE and b->nextin is the index

of the next empty slot in the buffer, or

b->occupied == BSIZE and b->nextin is the index of the

next (occupied) slot that will be emptied by a consumer

(such as b->nextin == b->nextout) */



pthread_cond_signal(&b->more);



pthread_mutex_unlock(&b->mutex);

}

请注意 assert() 语句的用法。除非在编译代码时定义了 NDEBUG,否则 assert() 在其参数的计算结果为真(非零)时将不执行任何操作。如果参数的计算结果为假(零),则该程序会中止。在多线程程序中,此类断言特别有用。如果断言失败,assert() 会立即指出运行时问题。assert() 还有另一个作用,即提供有用的注释。

 /* now: either b->occupied ... 开头的注释最好以断言形式表示,但是由于语句过于复杂,无法用布尔值表达式来表示,因此将用英语表示。

断言和注释都是不变量的示例。这些不变量是逻辑语句,在程序正常执行时不应将其声明为假,除非是线程正在修改不变量中提到的一些程序变量时的短暂修改过程中。当然,只要有线程执行语句,断言就应当为真。

使用不变量是一种极为有用的方法。即使没有在程序文本中声明不变量,在分析程序时也应将其视为不变量。

每次线程执行包含注释的代码时,生成方代码中表示为注释的不变量始终为真。如果将此注释移到紧挨 mutex_unlock() 的后面,则注释不一定仍然为真。如果将此注释移到紧跟assert() 之后的位置,则注释仍然为真。

因此,不变量可用于表示一个始终为真的属性,除非一个生成方或一个使用者正在更改缓冲区的状态。线程在互斥锁的保护下处理缓冲区时,该线程可能会暂时声明不变量为假。但是,一旦线程结束对缓冲区的操作,不变量即会恢复为真。

示例 4–13 给出了使用者的代码。该逻辑流程与生成方的逻辑流程相对称。


示例 4–13 生成方和使用者问题:使用者

 

char consumer(buffer_t *b)

{

char item;

pthread_mutex_lock(&b->mutex);

while(b->occupied <= 0)

pthread_cond_wait(&b->more, &b->mutex);



assert(b->occupied > 0);



item = b->buf[b->nextout++];

b->nextout %= BSIZE;

b->occupied--;



/* now: either b->occupied > 0 and b->nextout is the index

of the next occupied slot in the buffer, or

b->occupied == 0 and b->nextout is the index of the next

(empty) slot that will be filled by a producer (such as

b->nextout == b->nextin) */



pthread_cond_signal(&b->less);

pthread_mutex_unlock(&b->mutex);



return(item);

}

笔记整理--Linux多线程

原文:http://www.cnblogs.com/stlong/p/6290354.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!