一、竞争与同步
当多个线程同时访问其共享资源时,由于操作时间不协调,导致数据不一致、不完成,这种现象叫同步。
而我们需要在访问共享资源时达到异步的效果。
二、互斥量(锁)
pthread_mutex_t
互斥锁,是解决同步问题的一项技术。
int pthread_mutex_init (pthread_mutex_t *__mutex,const pthread_mutexattr_t *__mutexattr)
功能:初始一个互斥锁,也可以使用宏PTHREAD_MUTEX_INITIALIZER初始化
mutexattr:锁的属性,可以为NULL,使用缺省的参数。
返回值:成功返回0,失败返回错误码。
int pthread_mutex_destroy (pthread_mutex_t *__mutex)
功能:销毁一个互斥锁
int pthread_mutex_lock (pthread_mutex_t *__mutex)
功能:执行加锁操作,如果该已经处于锁的状态,则该线程阻塞
int pthread_mutex_unlock (pthread_mutex_t *__mutex)
功能:执行解锁操作
int pthread_mutex_trylock (pthread_mutex_t *__mutex)
功能:添加测试锁,如果不加锁刚立即返回
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,
const struct timespec *restrict abs_timeout);
struct timespec
{
time_t tv_sec; /* Seconds. */
long int tv_nsec; /* Nanoseconds.*/ 1秒= 1000000000 纳秒
};
功能:添加时锁,倒计时,如果超时还不加上则立即返回。
三、死锁
什么是死锁:多个线程互相等待对方资源,在得到所需要的资源之前都不会释放自己的资源,然后造成循环等待的现象,称为死锁。
死锁产生四大必要条件:只要有一个不满足就不能构成死锁。
1、资源互斥
2、占有且等待
3、资源不可剥夺
4、环路等待
如休防止出现死锁:四个条件只有一个被破坏,就不能构成死锁。、
1、破坏互斥条件,让资源能够共享使用(准备多份)。
2、破坏占且等待的条件,一次申请完成它所有需要的资源,资源没有满足前不让它运行,一旦开始运行就一直归它所有。
缺点:系统资源会被浪费。
3、破坏不可剥夺的条件,当已经占有了一些资源,请求新的资源而获取不到,然后就释放已经获取到的资源。
缺点:实现起来比较复杂,释放已经获取到的资源可能会造成前一阶段的工作浪费。
4、破坏循环等待的条件,采用顺序分配资源的方法,在系统中为资源进行编号,规定线程必须按照编号递增的顺序获取资源。
缺点:资源必须相对稳定,这样就限制了资源的增加和减少。
四、条件变量
条件变量可以让线程在满足特定的条件下暂停或继续,要和互斥量配合使用(线程自己进入休眠,然后被别人唤醒)。
pthread_cond_t
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
功能:初始化条件变量,也可以使用宏PTHREAD_COND_INITIALIZER初始化。
int pthread_cond_destroy(pthread_cond_t *cond);
功能:销毁条件变量
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 abstime);
功能:让当前线程进入暂停状态(睡入条件变量),且设置它的睡眠时间
int pthread_cond_signal(pthread_cond_t *cond);
功能:从一个条件变量中唤醒一个线程
注意:线程醒的瞬间会再次把锁加上,如果不能加上会继续等待加锁。
int pthread_cond_broadcast(pthread_cond_t *cond);
功能:从条件变量中唤醒所有线程
五、生产者与消费模型
生产者:产生数据的线程
消费者:使用数据的线程
仓库:数据缓冲区
问题1:生产快,消费慢 则会爆仓
问题2:消费快,生产慢 则会饿死
解决方案:使用互斥锁与条件变量解决。
爆仓 让产生数据的线程睡入爆仓条件变量,同时唤醒饿死条件变量中的线程。
饿死 让使用数据的线程睡入饿死条件变量,同时唤醒爆仓条件变量中的线程。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#define MAX_SIZE 50 // 容量
char arr[MAX_SIZE]; // 仓库
size_t cnt = 0; // 库存
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; // 访问仓库的锁
pthread_cond_t full = PTHREAD_COND_INITIALIZER; // 仓库满的条件变量
pthread_cond_t empty = PTHREAD_COND_INITIALIZER; // 仓库空的条件变量
void show(const char* who,const char* op,char ch)
{
for(int i=0; i<cnt; i++)
{
printf("%c",arr[i]);
}
printf("%s%c:%s\n",op,ch,who);
}
// 生产者线程
void* producer(void* arg)
{
const char* who = arg;
for(;;)
{
// 加锁访问仓库
pthread_mutex_lock(&lock);
// 判断仓库是否满
while(cnt >= MAX_SIZE)
{
printf("%s:满仓!\n",who);
// 睡入满仓条件变量
pthread_cond_wait(&full,&lock);
}
char ch = 'A' + rand()%26;
show(who,"<--",ch);
arr[cnt++] = ch;
// 叫一个消费者
pthread_cond_signal(&empty);
pthread_mutex_unlock(&lock);
usleep(rand()%100*1000);
}
}
// 消费者线程
void* customer(void* arg)
{
const char* who = arg;
for(;;)
{
// 加锁访问仓库
pthread_mutex_lock(&lock);
// 判断仓库是否为
while(0 == cnt)
{
printf("%s:空仓!\n",who);
// 睡入空仓条件变量
pthread_cond_wait(&empty,&lock);
}
char ch = arr[cnt--];
show(who,"->",ch);
// 叫醒一个生产者
pthread_cond_signal(&full);
pthread_mutex_unlock(&lock);
usleep(rand()%100*1000);
}
}
int main()
{
srand(time(NULL));
pthread_t tid[6];
for(int i=0; i<3; i++)
{
pthread_create(&tid[i],NULL,producer,"生产");
}
for(int i=3; i<6; i++)
{
pthread_create(&tid[i],NULL,customer,"消费");
}
for(int i=0; i<6; i++)
{
pthread_join(tid[i],NULL);
}
}
六、信号量(跟进程的类似)
信号量是一个计数器,与进程的信号量原理相同,用于控制访问共享资源的线程数量。
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:获取一个信号量并初始化
sem:是一个输出性质的参数,用于获取信号量的ID
pshared:
0 表示只在当时进程下使用
非0 表示多个进程间共享使用(相当于进程间使用的信号量,但是Linux系统不支持)。
value:信号量初始值
int sem_wait(sem_t *sem);
功能:对信号量进行减一操作,不够减则阻塞
int sem_trywait(sem_t *sem);
功能:对信号量进行减一操作,不够减则返回非零值
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
功能:对信号量进行减一操作,不够减则倒计时,超时后返回非零值
int sem_post(sem_t *sem);
功能:对信号量进行加一操作
int sem_destroy(sem_t *sem);
功能:销毁信号量
int sem_getvalue(sem_t *sem, int *sval);
功能:获取信号量的值