系统编程.10 同步和互斥
1.同步和互斥
一个公共资源同一时刻只能被一个进程或线程访问。
互斥:当多个进程或线程需要访问同一个公共资源的时候,不能同时使用,需要抢占。
同步:当多个进程或线程需要访问同一个公共资源的时候,不能同时使用,需要按照约定好的顺序使用。
1.1 互斥锁
互斥锁是一种简单加锁的方式来控制共享资源访问的方法。
互斥锁有两种状态:加锁(lock)和解锁(unlock)。
操作流程:
- 在访问共享资源后临界区域前,对互斥锁进行加锁。 谁加上锁,就归谁使用
- 在访问完成后释放互斥锁导上的锁。
- 对互斥锁进行加锁后,任何其他试图再次对互斥锁加锁的线程将会被阻塞,直到锁被释放。
数据类型:pthread_mutex_t
1.2 API
1.2.1 pthread_mutex_init()
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutex_attr_t *restrict attr);
函数功能:
- 初始化一个互斥锁
参数:
- mutex:互斥锁地址。类型是 pthread_mutex_t 。
- attr:设置互斥量的属性,通常可采用默认属性,即可将 attr 设为 NULL。
返回值:
- 成功:0(申请成功的锁默认打开)
- 失败:非0错误码
静态初始化互斥锁:
使用宏 PTHREAD_MUTEX_INITIALIZER 静态初始化互斥锁,比如:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
等价于使用 NULL 指定的 attr 参数调用 pthread_mutex_init(&mutex ,NULL) 来完成动态初始化,不同之处在于THREAD_MUTEX_INITIALIZER 宏不进行错误检查。
1.2.2 int pthread_mutex_destroy()
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
函数功能:
- 销毁指定的互斥锁。
参数:
- mutex:互斥锁地址。
返回值:
- 成功:0
- 失败:非 0 错误码
1.2.3 pthread_mutex_lock()
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
函数功能:
- 对互斥锁上锁,若互斥锁已经上锁,则调用者阻塞,直到互斥锁解锁后再上锁。
参数:
- mutex:互斥锁地址。
返回值:
- 成功:0
- 失败:非 0 错误码
1.2.4 pthread_mutex_unlock()
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
函数功能:
- 对互斥锁解锁。
参数:
- mutex:互斥锁地址。
返回值:
- 成功:0
- 失败:非 0 错误码
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>pthread_mutex_t mutex;void *print(void *argv);
void *func1(void *argv);
void *func2(void *argv);int main(int argc, char const *argv[])
{pthread_t pt1,pt2;pthread_mutex_init(&mutex,NULL) ;pthread_create(&pt1,NULL,func1,"helloworld");pthread_create(&pt2,NULL,func2,"goodnightworld");pthread_join(pt1,NULL);pthread_join(pt2,NULL);pthread_mutex_destroy(&mutex);return 0;
}void *print(void *argv)
{int i=0;char *p=(char *)argv;while (*p!='\0'){printf("%c",p[i]);fflush(stdout);i++;sleep(1);}return 0;
}void *func1(void *argv)
{pthread_mutex_lock(&mutex);print(argv);pthread_mutex_unlock(&mutex);return NULL;
}
void *func2(void *argv)
{pthread_mutex_lock(&mutex);print(argv);pthread_mutex_unlock(&mutex);return NULL;
}
实现互斥只需要一把锁。
2.死锁
情况1:线程抢占资源后,由于某种原因突然消失,导致一直无法解锁。
情况2:两个线程同时执行,一个线程申请另一个正在使用的资源
情况3:两个线程同时申请另一个正在使用的资源
解决方法:
- 进程开始前,一次性申请需要的所有资源
- 允许进程只获得初期的资源就开始运行,运行完释放,再申请新的资源
3.条件变量
条件变量是用来等待而不是用来上锁的,条件变量本身不是锁!
条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。
通常条件变量和互斥锁同时使用。
条件变量的两个动作: 条件不满, 阻塞线程 当条件满足, 通知阻塞的线程开始工作。
3.1 API
3.1.1 初始化
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
函数功能:
- 初始化一个条件变量
参数:
cond:指向要初始化的条件变量指针。
attr:条件变量属性,通常为默认值,传 NULL 即可
返回值:
- 成功:0
- 失败:非 0 错误码
静态初始化:pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
等价于:pthread_cond_init(&cond,NULL)
3.1.2 pthread_cond_destroy()
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
函数功能:
- 销毁一个条件变量
参数:
cond:指向要初始化的条件变量指针。
返回值:
- 成功:0
- 失败:非 0 错误码
3.1.3 pthread_cond_wait()
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
函数功能:
- 阻塞等待一个条件变量
a) 阻塞等待条件变量 cond(参 1)满足
b) 释放已掌握的互斥锁(解锁互斥量)相当于 pthread_mutex_unlock(&mutex);
a)b) 两步为一个原子操作(一起执行,没有中间态)。
c) 当被唤醒,pthread_cond_wait 函数返回时,解除阻塞并重新申请获取互斥锁 pthread_mutex_lock(&mutex);
参数:
- cond:指向要初始化的条件变量指针
- mutex:互斥锁
返回值:
- 成功:0
- 失败:非 0 错误号
3.1.4 唤醒所有
int pthread_cond_broadcast(pthread_cond_t *cond);
函数功能:
- 唤醒全部阻塞在条件变量上的线程
参数:
- cond:指向要初始化的条件变量指针
返回值:
- 成功:0
- 失败:非 0 错误号
4. 读写锁
特点:
- 如果有其它线程读数据,则允许其它线程执行读操作,但不允许写操作。
- 如果有其它线程写数据,则其它线程都不允许读、写操作
规则:
- 如果某线程申请了读锁,其它线程可以再申请读锁,但不能申请写锁。
- 如果某线程申请了写锁,其它线程不能申请读锁,也不能申请写锁。
4.1 API
4.1.1 pthread_rwlock_init()
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
函数功能:
- 用来初始化 rwlock 所指向的读写锁。
参数:
- rwlock:指向要初始化的读写锁指针。
- attr:读写锁的属性指针。如果 attr 为 NULL 则会使用默认的属性初始化读写锁,否则使用指定的 attr 初始化读写锁。
返回值:
- 成功:0,读写锁的状态将成为已初始化和已解锁。
- 失败:非 0 错误码。
静态初始化读写锁:
pthread_rwlock_t my_rwlock = PTHREAD_RWLOCK_INITIALIZER;
等价于pthread_rwlock_init(&rwlock,NULL) 来完成动态初始化,不同之处在于 PTHREAD_RWLOCK_INITIALIZER 宏不进行错误检查。
4.1.2 int pthread_rwlock_destroy()
#include <pthread.h>
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
函数功能:
- 用于销毁一个读写锁,并释放由 pthread_rwlock_init() 自动申请的资源 。
参数:
- rwlock:读写锁指针。
返回值:
- 成功:0。
- 失败:非 0 错误码。
4.1.3 pthread_rwlock_rdlock()
#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
函数功能:
如果调用线程未获取读锁,则它将阻塞直到它获取了该锁。一个线程可以在一个读写锁上多次执行读锁定。(如果有人在写,那么就不能申请读锁,所以会阻塞)
线程可以成功调用 pthread_rwlock_rdlock() 函数 n 次,但是之后该线程必须调用 pthread_rwlock_unlock() 函数 n。次才能解除锁定。(保持次数一致)
参数:
- rwlock:指向要初始化的读写锁指针。
返回值:
- 成功:0。
- 失败:非 0 错误码。
4.1.4 pthread_rwlock_wrlock()
#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
函数功能:
如果调用线程未获取写锁,则它将阻塞直到它获取了该锁。(因为别的线程写的时候,不能申请写锁,所以会阻塞)
参数:
- rwlock:指向要初始化的读写锁指针。
返回值:
- 成功:0。
- 失败:非 0 错误码。
4.1.5 pthread_rwlock_unlock()
#include <pthread.h>
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
函数功能:
- 无论是读锁或写锁,都可以通过此函数解锁。
参数:
- rwlock:读写锁指针。
返回值:
- 成功:0
- 失败:非 0 错误码