互斥锁
举例:喂鱼问题
Alice和Tom,只为喂一次鱼
/*方法一*/
// Alice
lock()
if (no feed)
{
feed fish
}
unlock()
// Tom
lock()
if (no feed)
{
feed fish
}
unlock()
互斥锁的上锁和解锁
上锁和解锁都是原子操作
上锁
- 等待锁至打开状态
- 获得锁并锁上
解锁
信号量
简介
信号量是一个计数器,与其它进程间通信方式不大相同,它主要用于控制多个进程间或一个进程内的多个线程间对共享资源的访问,相当于内存中的标志,进程可以根据它判定是否能够访问某些共享资源,同 时,进程也可以修改该标志,除了用于共享资源的访问控制外,还可用于进程同步。
它常作为一种锁机制,防止某进程在访问资源时其它进程也访问该资源,因此,主要作为进程间以及同一个进程内不同线程之间的同步手段
信号量是一个整数,除了初始化时可以被赋值外,只能通过P、V操作对其进行操作
信号量的实现
P(s)
{
while (s <= 0)
{
do nothing
}
s--;
}
V(s)
{
s++;
}
信号量使用
二值信号量
简介
二值信号量的值只能是0或1,通常将其初始化为1,用于实现互斥锁的功能。
semaphore mutex = 1;
process Pi {
P(mutex);
Critical section
V(mutex);
}
一般信号量
一般信号量的取值可以是任意数值,用于控制并发进程/线程对共享资源的访问。
使用
#include <semaphore.h>
sem_t:信号量的数据类型
/*
* sem: 信号量
* pshared:信号量的类别,0表示进程内的线程都可用,非0表示不同进程间可用
* val: 信号量的初始值
*/
int sem_init(sem_t *sem, int pshared, unsigned int val);
/*
* 相当于P操作
*/
int sem_wait(sem_t *sem);
/*
* 相当于V操作
*/
int sem_post(sem_t *sem);
/*
* 销毁信号量
*/
int sem_destroy(sem_t *sem);
#include <semaphore.h>
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static sem_t s;
void *handler_p(void *data)
{
sem_wait(&s);
printf("(%lu) I INTEND to pass the fork\n", pthread_self());
sleep(1);
printf("(%lu) I AM AT the fork\n", pthread_self());
sleep(1);
printf("(%lu) I haved passed the fork\n", pthread_self());
sleep(1);
sem_post(&s);
pthread_exit(NULL);
}
int main()
{
sem_init(&s, 0, 3);
int i = 0;
pthread_t p1[5];
for (i = 0; i<5;i++){
pthread_create(&p1[i], NULL, handler_p, NULL);
}
for (i = 0; i<5;i++){
pthread_join(p1[i], NULL);
}
sem_destroy(&s);
pthread_exit(NULL);
}
信号量实现同步
- 同步问题实质是将异步的并发进程按照某种顺序执行
- 解决同步的本质是找到并发进程的交互点,利用P操作的等待特点来调节进程的执行速度
- 通常初始值为0的信号量可以让进程直接进行等待状态,知道另一个进程唤醒它。
- 初始值=1,相当于互斥锁
- 初始值>1,相当于可用资源数量
- 初始值=0,相当于同步问题
经典的同步问题
生产者-消费者问题
条件变量用于生产者-消费者问题
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
static pthread_mutex_t mutex; //定义互斥锁
static pthread_cond_t cond; //定义条件变量
static int g_avail = 0; //全局共享资源
/* 消费者线程 */
static void *consumer_thread(void *arg)
{
for ( ; ; ) {
pthread_mutex_lock(&mutex);//上锁
while (0 >= g_avail)
pthread_cond_wait(&cond, &mutex);//等待条件满足
while (0 < g_avail)
g_avail--; //消费
pthread_mutex_unlock(&mutex);//解锁
}
return (void *)0;
}
/* 主线程(生产者) */
int main(int argc, char *argv[])
{
pthread_t tid;
int ret;
/* 初始化互斥锁和条件变量 */
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);
/* 创建新线程 */
ret = pthread_create(&tid, NULL, consumer_thread, NULL);
if (ret) {
fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
exit(-1);
}
for ( ; ; ) {
pthread_mutex_lock(&mutex);//上锁
g_avail++; //生产
pthread_mutex_unlock(&mutex);//解锁
pthread_cond_signal(&cond);//向条件变量发送信号
}
exit(0);
}
苹果橘子问题
读写问题
死锁
产生死锁的四个必要条件
- 互斥使用:一个时刻,一个资源只能被一个进程使用
- 不可剥夺:除了资源占有进程主动释放资源,其他进程都不可抢夺其资源
- 占用和等待:一个进程的请求资源得不到满足等待时,不释放已经占用的资源
- 循环等待:每一个进程分别等待它前一个进程所占用的资源
死锁的解决方案
- 死锁的防止:破坏四个必要条件之一
- 死锁的避免:允许四个必要条件同时存在,在并发进程中妥善安排避免死锁的发生
- 死锁的检测和恢复:允许死锁的发生,系统及时检测并解除它