您的位置:首页 > 运维架构 > Linux

linux环境编程之多线程同步

2015-06-16 20:46 633 查看
多线程同步,当有多个线程同时访问共享内存时就会产生数据不一致性。所以为了保证数据的一致性必须让线程同步,同步方式有下面几种:

互斥量

互斥量从本质上来说是一把锁,在访问共享资源前对互斥量进行加锁,访问完后释放互斥量上的锁。对互斥量加锁后,任何其他的线程对互斥量加锁都会被阻塞,直到互斥量上的锁释放。当锁释放后,阻塞的所有线程都会变成可运行的,然后进行抢占。

互斥量用pthread_mutex_t数据类型来表示,在使用互斥量之前要先对其进行初始化。

静态初始化: pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

动态初始化:int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

注销互斥量:int pthread_mutex_destroy(pthread_mutex_t *mutex);

返回值:若成功则返回0,失败则返回错误码;

第一个参数:是互斥量的锁;注销互斥量函数的第二个参数为锁的属性,一般为NULL;

初始化后就准备对互斥量上锁了,如果互斥量已经上锁了,那试图上锁的线程将会阻塞直到锁释放。上锁函数如下:

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_trylock(pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthead_mutex_t *mutex);

返回值:成功返回0,失败返回错误码;

pthread_mutex_lock()函数对已经上锁的互斥量上锁会阻塞 直到锁释放。

pthread_mutex_trylock()函数则不阻塞等待,如果互斥量没有上锁,则上锁返回0;否则,失败返回错误码。

读写锁

互斥量虽然能有效的保证数据的一致性,但是这降低了数据的并行访问效率。因为互斥量只有两个状态,一个是上锁状态,一个是不上锁状态。如果多个线程都是只读不写,那么如果用互斥量就有点损失性能了。所以也就出现了读写锁,也就是共享-独占锁,当读写锁以读模式锁住,他则是共享的,其他读线程都可以再次上锁访问,这是共享锁。如果以写模式锁住,则是独占锁,这时和互斥量一样了。

下面是读写锁初始化函数:

int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

返回为0, 失败返回错误码;



在读模式下上读写锁函数:int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

在写模式下上读写锁函数:int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

不管在什么模式下,都使用同一个函数释放锁:int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

返回值:成功返回0,失败返回错误码;

这里读模式就是上读锁的意思,表示该线程只读临界区的数据,而不修改。写模式则是独占锁,可能会修改数据;

注意:读写锁不像互斥量,读写锁是不会阻塞的。

和互斥量一样,读写锁也有尝试加锁函数:

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); //读

int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); //写

返回值:成功返回0,失败返回错误码;

其实在读、写模式下的函数已经不再阻塞了,本来可以不用尝试加锁函数的,但是共享锁可能有线程数目的限制,所以可以用尝试加锁来加锁;

条件变量

条件变量是多线程下用来同步的另外一种机制。条件变量给多线程提供一个会合的场所,可以和互斥量一起使用,允许线程以无竞争的方式等待特定的条件发生。

条件变量本身是由互斥量保护的,线程在改变条件前必须要锁住互斥量,其他线程因为没有得到锁,所以不知道条件的改变。



条件变量初始化:

静态初始化:pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

动态初始化:pthread_cond_t cond;

int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *attr);//初始化函数

int pthread_cond_destroy(pthread_cond_t *cond);//注销条件函数

返回值:成功返回0,失败返回错误码;

初始化函数: pthread_cond_init()中的第二个参数为条件变量属性,一般设置NULL;

条件变量等待函数:

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *timeout);

返回值:成功返回0,失败返回错误码;

pthread_cond_wait()和pthread_cond_timedwait()函数类似,只是pthread_cond_timedwait()函数有个定时功能,如果在规定时间内条件没有变真,pthread_cond_wait()函数会一直睡眠(他没有时间概念);而pthread_cond_timedwait()则会醒来直接返回错误码。所以这里只分析pthread_cond_wait()函数;

当线程调用了pthread_cond_wait()函数,则线程会在等待条件的线程列表中休眠,然后解互斥量的锁(等待条件有个隐藏的互斥量的锁),这两个操作都是原子操作。

为什么要解开互斥量的锁呢?因为调用线程在等待条件变量的改变(要不然他一直睡眠着),如果他又一直锁着,其他线程则无法获取到锁(这是互斥量锁,独占的)也就无法修改条件变量,那么等待线程(也就是他自己)就会死锁了。

为什么要原子操作呢?其实pthread_cond_wait()函数应该是这样的,先进入条件变量的等待线程列表中,然后检查条件,不符合,则解开互斥量的锁,睡眠(不知道是先解锁还是先睡眠,但是先检查条件是否符合是肯定的)。所以这一系列操作要用原子操作,因为如果不是原子操作当检查完条件时,其他线程又更改了条件,使得条件满足了。但该线程刚检查完不符合,那么就会解锁睡眠等待了,可其他线程已经更改了条件。这就会造成死锁了。

当条件符合后,pthread_cond_wait()就会醒来,在该函数返回时,互斥量会被再次被锁住。

条件变量唤醒函数:

int pthread_cond_signal(pthread_cond_t *cond);// 唤醒条件变量上所有的等待线程

int pthread_cond_broadcast(pthread_cond_t *cond);//唤醒条件变量上的某个线程

返回值:成功返回0,失败返回错误码;



实例查看: 多线程的生产者和消费者问题

转载地址:/article/1530579.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: