当多个控制线程共享相同的内存时,需要确保每个线程看到一致的数据视图。如果是只读的就不存在这种情况,但是当一个线程修改变量时,其他线程在读取这个变量的值时就可能会看到不一致的数据。在变量修改时间多于一个存储器访问周期的处理器结构中,当存储器读与存储器写这两个周期交叉时,这种潜在的不一致性就会出现。
存储器写操作需要两个存储器周期,读只需要一个存储器周期。 要避免这种竞争,就得确保修改操作是原子操作。
三种线程同步方式:
(1)互斥量
(2)读写锁
(3)条件变量
1、互斥量(mutex)
互斥量本质上是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。如果释放互斥锁时同时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行态,第一个变为运行态的线程可以对互斥量进行加锁,其他互斥量将会看到互斥量依然被锁住,只能回去再次等待它重新变为可用。
互斥变量用pthread_mutex_t数据类型表示,在使用前必须先对其进行初始化,有两种方式对其初始化,一种是把它设置为常量PTHREAD_MUTEX_INITIALIZER(只对静态分配的互斥量);另一种是调用pthread_mutex_init()函数进行初始化。如果动态分配互斥量那么在释放内存前需要调用pthread_mutex_destroy()进行摧毁。
- #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);
- 返回值:成功返回0,失败返回错误编号;
如果使用默认初始化互斥量,只需把attr设置为NULL;
对互斥量进行加锁,需要调用pthread_mutex_lock,如果互斥量已经上锁,调用线程将会阻塞到互斥量被解锁。
- #include <pthread.h>
- int pthread_mutex_lock(pthread_mutex_t *mutex);
- int pthread_mutex_trylock(pthread_mutex_t *mutex);
- int pthread_mutex_unlock(pthread_mutex_t *mutex);
- 返回值:成功返回0,错误返回错误编号;
如果线程不希望被阻塞,它可以使用pthread_mutex_trylock尝试对互斥量进行加锁。如果调用pthread_mutex_trylock时互斥量处于未锁住状态,那么pthread_mutex_trylock将锁住互斥量,不会出现阻塞并返回0,否则失败不能锁住互斥量,而返回EBUSY;使用pthread_mutex_trylock()接口可以避免死锁。
2、读写锁(又名共享-独占锁)
读写锁与互斥量类似,不过读写锁允许更高的并行性。互斥量只有两种状态:加锁和不加锁;而且一次只有一个线程可以对其加锁。读写锁有三种状态:读模式下加锁状态,写模式下加锁状态,不加锁状态;一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。
当读写锁是写加锁状态时,在这个锁被解锁之前,所有试题对这个锁加锁的线程都会被阻塞。当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是如果线程希望写模式对此锁,它必须阻塞直到所有的线程释放读锁。虽然读写锁的实现各不相同,但当读写锁处于读模式锁住状态时,如果有另外的线程试图以写模式加锁,读写锁通常会阻塞随后的读模式锁请求。这样可以避免读模式锁长期占用,而等待的写模式锁请求一直得不到满足。(适用于对数据结构读的次数远远大于写的情况)。
和互斥量一样,读写锁在使用前必须初始化,在释放它们底层的内存前必须销毁。
- #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 *rwlock);
- 返回值:成功返回0,否则返回错误编号。
默认属性attr为NULL;
两种模式进行加锁,但是都要使用pthread_rwlock_unlock()进行解锁。
- #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);
- 返回值:成功返回0,失败返回错误编号;
3、条件变量 条件变量给多个线程提供了一个会和的场所。条件变量与互斥锁一起使用时,允许线程以无竞争的方式等待特定的条件发生。
条件本身由互斥量保护,线程在改变条件状态前必须先锁住互斥量,其他线程在获得互斥量之前不会察觉到这种变化,因为必须锁定互斥量以后才能计算条件。
和其他同步方式一样,条件变量使用之前必须首先进行初始化,pthread_cond_t数据类型代表的条件变量可用两种方式进行初始化。一种是常量PTHREAD_COND_INITIALIZER赋给静态分配的条件变量,另一种动态分配的调用pthread_cond_init函数进行初始化。释放是用pthread_cond_destroy去除初始化。
- #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,失败返回错误编号;
- #include<pthread.h>
- 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);
- 返回值:成功返回0,失败返回错误编号;
传递给pthread_cond_wait的互斥量对条件进行保护,调用者把锁住的互斥量传给函数。函数把调用线程放到等待条件的线程列表上,然后对互斥量解锁,这两个操作是原子操作。这样就关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道。这样线程就不会错过条件的任何变化。pthread_cond_wait返回时,互斥量再次被锁住。
有两个函数可以用于通知线程条件已经满足。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,失败返回错误编号;
阅读(494) | 评论(0) | 转发(0) |