线程同步有关知识总结

线程同步:当多个控制线程共享相同的内存时,需要确保每个线程看到一致的数据视图。

 

两个或多个线程试图在同一时间修改同一变量时,需要进行同步。增量操作通常分解为以下3步:

       ·从内存单元读入寄存器

       ·在寄存器中对变量做增量操作

       ·把新的值写回内存单元

 

如果两个线程试图几乎同一时间对同一个变量做增量操作而不进行同步到话,结果就可能出现不一致。

 

为了确保同一时间只有一个线程访问数据,可以使用pthread的互斥接口来保护数据。

互斥量(mutex:本质上是一把锁。在访问共享资源前对互斥量进行设置(加锁),在访问完成后释放(解锁)互斥量。
对互斥量进行加锁后, 任何其他试图再次对互斥量加锁的线程都会被阻塞知道当前线程释放该互斥锁。

 

只有将所有线程都设计成遵守相同数据访问规则的,互斥机制才能正常工作。

 

互斥变量用 pthread_mutex_t 数据类型表示。

在使用互斥变量以前,必须首先对它进行初始化,可以将其设置为常量PTHREAD_MUTEX_INITIALIZER(只适合静态分配的互斥量),也可以通过调用pthread_mutex_init函数进行初始化。如果是动态分配互斥量,比如通过malloc函数,那么释放内存前需要调用pthread_mutex_destory。

#include <phtread.h>

int pthread_mutex_init(pthread_mutex_t *restrict mutex,

                        const phtread_mutexattr_t *restrict attr);

int phtread_mutex_destory(phtread_mutex_t *mutex);

                                    返回值:若成功,返回0;否则,返回错误编号

 

对互斥量进行加锁,需要调用pthread_mutex_lock。如果互斥量已经上锁,调用线程将阻塞直到互斥量被解锁。

对互斥量解锁,需要调用pthread_mutex_unlock。

Pthread_mutex_trylock尝试对互斥量进行加锁,如果互斥量未被锁住,则将其锁住,不会出现阻塞直接返回0,如果互斥量已经被锁住,则失败,不能锁住互斥量,返回EBUSY。该函数可以用来查看锁的占用情况。

#include <phtread.h>

int phtread_mutex_lock(pthread_mutex_t *mutex);

int phtread_mutex_trylock(phtread_mutex_t *mutex);

int phtread_mutex_unlock(phtread_mutex_t *mutex);

                                           返回值:若成功,返回0;否则,返回错误编号

 

如果线程试图对同一个互斥量加锁两次,那么它自身就会陷入死锁状态。另外某些不太明显的方式下使用互斥量也会造成死锁。

可以通过仔细控制互斥量加锁的顺序来避免死锁的发生。固定层次加锁。

如果涉及太多的锁和数据结构,可以先释放占有的锁,然后过一段时间再试。该情况下可以用pthread_mutex_trylock接口来避免死锁。试加锁和回退。

 

#include <pthread.h>

pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,const struct timespec *restrict tsptr);

                                                                      返回值:成功返回0,失败返回错误编号

Pthread_mutex_timedlock函数可以避免永久阻塞。在达到超时时间值时,该函数便不会对互斥量进行加锁,而是返回错误码ETIMEDOUT

 

 

读写锁

读写锁(reader-writer lock)与互斥量类似,不过读写锁允许更高的并行性。

读写锁有三种状态:

·读模式下加锁状态

·写模式下加锁状态

·不加锁状态

一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。

当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。

当读写锁是读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是任何以写模式对此锁进行加锁的线程都会阻塞,直到所有线程释放它们的读锁为止。、

 

一般情况下,当读写锁处于读模式加锁状态时,随后有一个线程请求写模式获取锁时,读写锁通常会阻塞随后的读模式锁请求,避免读模式锁长期占用,写模式锁一直得不到请求的状况。

 

读写锁在使用之前必须初始化,在释放它们底层的内存之前必须销毁

#include <pthread.h>

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,

const pthtread_rwlockattr_t *restrict attr);

int phtread_rwlock_destroy(pthread_rwlock_t *rwlock);

                                                 成功返回0,失败返回错误编号

 

#include <pthread.h>

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);//读模式下加锁

int pthread_rwlock_wrlock(pthrad_rwlock_t *rwlock);//写模式下加锁

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);//解锁

                                                 成功返回0,失败返回错误编号

 

条件变量:条件变量是线程可用的另一种同步机制。条件变量是用来等待线程而不是上锁的。

条件变量与互斥量一起使用,允许线程以无竞争的方式等待特定的条件发生。

条件本身是由互斥量保护的。线程在改变条件状态之前必须首先锁住互斥量。其他线程在获得互斥量之前不会改变,因为互斥量必须在锁定之后才能够计算条件。

 

条件变量使用之前,必须要进行初始化。

条件变量的数据类型为 pthread_cond_t

静态初始化:PTHREAD_COND_INITIALIZER

动态初始化:pthread_cond_init函数

在释放条件变量底层的空间之前,需要对其反初始化:

pthread_cond_destroy函数

 

#include <pthread.h>

int pthread_cond_init(pthread_cond_t *restrict cond,

const pthread_condattr_t *restrict attr);

int pthread_cond_destroy(pthread_cond_t *cond);

 

条件变量等待函数,如果在给定的时间内条件不能满足,那么会生成一个返回错误码的变量。

#include <pthread.h>

int pthread_cond_wait(pthread_cond_t *restrict cond,

pthread_mutex_t *restrict mutex);

int pthread_cond_timewait(pthread_cond_t *restrict cond,

const struct timespec *restrict tsptr);

                                          若成功返回0,若失败返回错误编号

调用者将锁住的互斥量传递给函数。函数然后自动把调用线程放到等待条件的线程列表上,并对互斥量解锁。一旦另一个线程修改了环境变量,就会通知相应的环境变量唤醒一个或者多个被这个条件变量阻塞的线程。这些被唤醒的线程将重新上锁,并测试条件是否满足。

 

有两个函数用来通知线程条件已经满足:

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

                                                 若成功返回0,若失败,返回错误编号

在调用pthread_cond_signal或者pthread_cond_broadcast时,这是在给线程或者条件发信号。必须注意:一定要在改变条件状态以后再给线程发信号。

条件变量使用while循环判断而不适用if判断。

 

总结:条件是工作队列的状态。互斥量用来保护条件,在while循环中判断条件。把消息放到工作队列时,需要占有互斥量,但在等待线程发信号时,不需要占有互斥量。因为我们是利用while循环来检查条件,所以当线程醒来时,发现队列仍然为空,然后就返回继续等待。
 

 

自旋锁:与互斥量类似,但是其是在获取锁之前一直处于忙等阻塞状态。

自旋锁的适用情况:锁被持有的时间段,而且线程并不希望在重新调度上花费太多成本。

自旋锁在非抢占式内核中是非常有效的。

 

 

屏障(barrier:是用户协调多个线程并行工作的同步机制。屏障允许每个线程等待,直到所有的而合作线程都到达某一点,然后从该点继续进行。

 

pthread_barrier_init进行初始化,pthread_barrier_destroy函数反初始化。

#include <pthread.h

int pthread_barrier_init(pthread_barrier_t *barrier,

                                          const pthread_barrierattr_t *restrict attr,

                                          unsigned int count);

int pthread_barrier_destroy(pthread_barrier_t *barrier);

                                           如果成功返回0如果失败返回错误编号

 

pthread_barrier_wait函数来表明线程已经完成工作,准备等待所有其他线程。

#include <pthread.h>

int pthread_barrier_wait(pthread_barrier_t *barrier);

                            如果成功返回0或者PTHREAD_BARRIER_SERIAL_THREAD;

                            如果失败返回错误编号

 

调用pthread_barrier_wait的线程在屏障计数count未满足条件时,会进入休眠状态。如果该线程是最后一个调用pthread_barrier_wait的线程,就满足了屏障计数,所有的线程都被唤醒。对于一个任意线程,pthread_barrier_wait函数返回PTHREAD_BARRIER_SERIAL_THREAD。剩下的线程看到的返回值是0。这使得一个线程可以作为主线程,它可以工作在其他所有线程已完成的工作结果上。

 

同步属性

互斥量属性: 进程共享属性、健壮属性、类型属性

         ·进程共享属性:多个进程可以访问同一个同步对象。

         ·健壮属性:与多个进程共享的互斥量有关。

         ·类型属性:控制着互斥量的锁定特性。

 

读写锁属性: 唯一属性是:进程共享属性。

 

条件变量属性:进程共享属性、时钟属性

         ·时钟属性控制pthread_cond_timewait函数的超时参数采用的是哪个时钟

屏障属性:只有进程共享属性。

         ·进程共享属性:控制着屏障是可以被多进程的线程使用还是只能被初始化屏障的进程内的多线程使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值