*********************************
互斥锁和条件变量相结合实现生产者及消费者模型
一、线程的创建
Linux系统的线程创建函数使用pthread_create。
原型:
#include <pthread.h>
int pthread_create(pthread_t *restrict thread_id,
const pthread_attr_t * restrict attr,
void * (*start_routine)(void *),
void * restrict arg );
第一个参数线程创建成功后得到线程id,thread_id(pthread_t *类型)。
第二个参数是需要设置线程的属性,包括线程栈大小等信息,attr(pthread_attr_t*)。如果不关心,设置NULL。
第三个参数是线程执行的函数,类型如void * (*start_routine)(void *)。
第四个参数为start_routine执行函数的传入参数,如果没有要传入的数据,设置为NULL。
二、互斥锁
1.互斥锁的创建、初始化及销毁方式
互斥锁的初始化的有静态和动态两种。
(1)静态初始化其实跟一般的变量初始化差不多,
POSIX定义了一个宏PTHREAD_MUTEX_INITIALIZER来静态初始化互斥锁,方法如下:
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
采用宏PTHREAD_MUTEX_INITIALIZER初始化,必须在定义的初始化,这是因为在 pthread.h 中,
PTHREAD_MUTEX_INITIALIZER
定义为:
#define PTHREAD_MUTEX_INITIALIZER \
{ { 0, 0, 0, 0, 0, 0, { 0, 0 } } }
当然,也可以通过调用函数pthread_mutex_init来初始化。
原型:
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t * restrict mutex,
const pthread_mutexattr_t * restrict attr );
使用函数初始化,按默认方式:
pthread_mutex_t mutex ;
pthread_mutex_init( &mutex, NULL );
(2)互斥锁的动态创建及初始化方法
动态创建,定义为指针变量。
如下:
pthread_mutex_t *pMutex = NULL;
pMutex =(pthread_mutex_t * ) malloc( sizeof(pthread_mutex_t ) ) ;
pthread_mutex_init(pMutex , NULL );
需要申请内存。销毁之后要记得释放内存,不然会内存泄漏。
同时程序或者模块结束时互斥锁要记得销毁,不然是资源泄漏。
(3)互斥锁的销毁方法
销毁函数为:
int pthread_mutex_destroy( pthread_mutex_t * mutex );
2.互斥锁的使用
(1)加锁函数
int pthread_mutex_lock(pthread_mutex_t * mutex);
int pthread_mutex_trylock(pthread_mutex_t * mutex);
注意:pthread_mutex_trylock执行成功,返回0 的话,以为着已经成功加锁。
(2)解锁函数
int pthread_mutex_unlock( pthread_mutex_t * mutex );
(3)互斥操作
pthread_mutex_lock()
...
Do Something Work
...
pthread_mutex_unlock()
需要的注意的地方,加锁之后,未解锁之前,其他线程请求加锁时,会进入阻塞状态,直到这边解锁为止。
因此,任何地方加锁,在返回的时候的要记得解锁,不然进程就死锁了。
原则上,谁加锁,谁就来解锁。
3.死锁的产生及解决
(1)任何地方线程加锁,在返回的时候的要记得解锁,不然进程中如果其他线程申请加锁的时候就永远阻塞了,这就造成了死锁了。
(2)两个锁以上的情况。以不同的线程以相反的顺序加锁,很容易造成死锁。
(3)同个线程在加锁成功后,未解锁的情况下,重新申请加锁。
三、条件变量
1.条件变量的初始化及销毁
(1)条件变量的静态初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER ;(静态方法)
或者调用函数pthread_cond_init初始化。(常用)
原型: int pthread_cond_init( pthread_cond_t * restrict cond,
const pthread_condattr_t * restrict attr) ;
例子:
pthread_cond_t cond;
pthread_cond_init(&cond, NULL); (默认NULL)
(2)条件变量的动态初始化
pthread_cond_t * pCond;
pCond = (pthread_cond_t *)malloc( sizeof(pthread_cond_t ) );
pthread_cond_init(pCond , NULL);
(3)条件变量的销毁
销毁函数:
int pthread_cond_destroy( pthread_cond_t *cond );
注意:如果是销毁动态的条件变量,销毁后需要释放内存。
模块终结时,也必须销毁条件变量,不然也是资源泄漏。
另外该函数要配合pthread_cond_broadcast使用,不然是无法destroy成功的。
2.条件变量的使用
(1)条件等待函数
pthread_cond_timedwait限时等待,超时返回ETIMEDOUT
int pthread_cond_timedwait( pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);
pthread_cond_wait无限时等待
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
(2)条件激活函数
#include <pthread.h>pthread_cond_broadcast激活所有挂起的等待
int pthread_cond_broadcast(pthread_cond_t *cond);
pthread_cond_signal只激活一个
int pthread_cond_signal(pthread_cond_t *cond);3.条件变量的应用
条件变量即跟条件的有关系。设定条件判断,如 if ( x < y )时,进入条件等待,线程挂起(pthread_cond_wait或pthread_cond_timedwait),等待条件不满足时,唤醒线程。也就是说,当if (x >= y )时,由另外线程调用
条件激活函数pthread_cond_signal (或pthread_cond_broadcast)去激活挂起的线程。
换句话来说,挂起的线程就等着条件if (x >= y )满足。
因此,条件变量的好处是避免了死锁和竞争的问题,提高了cpu的效率。
pthread_cond_wait(或pthread_cond_timedwait)需要结合互斥锁使用
如下:
pthread_mutex_lock(&mutex);
if ( x < y )
pthread_cond_wait(&cond, &mutex);
...
do something work
pthread_mutex_unlock(&mutex);
pthread_cond_wait需要传条件变量和互斥锁进去,当进入该函数,会先解锁mutex,然后线程加入等待唤醒队列,当被唤醒时,会重新对mutex进行加锁,线程起来。
因此加锁和解锁要保持一致。
pthread_cond_signal 只激活一个条件变量挂起的线程。pthread_cond_signal 的使用,可以加锁也可以不加。