线程同步
概念
线程同步是指多个线程在同一时间访问同一资源时,为了保证数据的正确性和一致性,需要对线程的执行顺序进行控制,以保证数据的完整性和正确性。
信号量
信号量的概念
信号量是一个非负整数,其值代表系统中某一资源可供使用的数量,由信号量决定线程是继续执行还是阻塞等待。
信号量是一个受保护的值,只能通过三种方式来访问:初始化、P操作(申请资源)和V操作(释放资源)。
信号量的特点
当信号量大于0时,P操作将信号量减1,并允许一个线程继续执行;当信号量等于0时,P操作阻塞线程,直到其他线程释放了资源;当信号量小于0时,V操作将信号量加1,唤醒一个阻塞的线程。
信号量的分类
1.posix信号量
1)无名信号量(sem_open):只能在同一进程内使用,只能在有名信号量之前使用。
2)有名信号量(sem_init):可以在不同进程间共享,可以在任意时刻使用。
2.system V信号量
也叫信号灯集,属于ICP对象。
相关函数
初始化信号量
ses_init
定义:
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:
初始化信号量。
参数:
sem:信号量的地址。
pshared:信号量共享属性,0表示线程间共享,1表示进程间共享。
value:信号量的初始值。
返回值:
成功返回0,出错返回-1。
申请资源
sem_wait
定义:
int sem_wait(sem_t *sem);
功能:
申请资源,如果信号量大于0,则将信号量减1,并允许一个线程继续执行;否则,阻塞线程,直到其他线程释放了资源。
参数:
sem:信号量的地址。
返回值:
成功返回0,出错返回-1。
释放资源
sem_post
定义:
int sem_post(sem_t *sem);
功能:
释放资源,将信号量加1,唤醒一个阻塞的线程。
参数:
sem:信号量的地址。
返回值:
成功返回0,出错返回-1。
销毁信号量
sem_destroy
定义:
int sem_destroy(sem_t *sem);
功能:
销毁信号量。
参数:
sem:信号量的地址。
返回值:
成功返回0,出错返回-1。
线程互斥
概念
线程互斥是指多个线程在同一时间访问同一资源时,只允许一个线程访问,其他线程必须等待,直到该线程访问结束后,其他线程才能访问。
互斥锁
线程同步相当于另一种线程互斥,但是线程互斥不是另一种线程同步,线程同步是按照规定的顺序执行,而线程互斥不需要按顺序执行,只需保证同一时刻只有一个线程来访问共享资源即可。
所有的共享资源只需要一把锁即可达到线程互斥的目的,比如一个人在使用卫生间洗澡时,他只需要关上一道门,别人也无法使用卫生间的其它功能。
函数
pthread_mutex_init
定义:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
功能:
初始化互斥锁。
参数:
mutex:互斥锁的地址。
attr:互斥锁属性,可以为NULL。
返回值:
成功返回0,出错返回非0。
pthread_mutex_lock
定义:
int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:
申请互斥锁,如果互斥锁已经被其他线程锁定,则阻塞线程,直到互斥锁可用。
参数:
mutex:互斥锁的地址。
返回值:
成功返回0,出错返回非0。
pthread_mutex_unlock
定义:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能:
释放互斥锁。
参数:
mutex:互斥锁的地址。
返回值:
成功返回0,出错返回非0。
pthread_mutex_destroy
定义:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能:
销毁互斥锁。
参数:
mutex:互斥锁的地址。
返回值:
成功返回0,出错返回非0。
死锁
死锁是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
死锁的必要条件
1.互斥
当一个进程或线程在使用资源时,其他进程或线程不能访问该资源。
2.请求和保持
进程或线程在请求其他资源的同时保持原有资源的占有。
3.不剥夺(不可抢占)
资源请求者不能从资源占有者手中夺取资源,只能由资源占有者主动释放资源。
4.环路等待(循环等待)
每个进程都在等待下一个进程所占用的资源,而下一个进程又在等待当前进程所占用的资源,形成一个环路。
条件变量
条件变量是一种同步机制,在某一线程没有接收到条件变量时会发生阻塞,直到另一线程发出条件变量时才会被唤醒。条件变量通常搭配互斥锁一起使用,可以达到线程同步的效果。
函数
pthread_cond_init
定义:
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
功能:
初始化条件变量。
参数:
cond:条件变量的地址。
attr:条件变量属性,可以为NULL。
返回值:
成功返回0,出错返回非0。
pthread_cond_wait
定义:
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
功能:
等待条件变量,如果条件变量没有被唤醒,则阻塞线程,直到条件变量被唤醒。
参数:
cond:条件变量的地址。
mutex:互斥锁的地址。
返回值:
成功返回0,出错返回非0。
pthread_cond_signal
定义:
int pthread_cond_signal(pthread_cond_t *cond);
功能:
唤醒一个等待条件变量的线程。
参数:
cond:条件变量的地址。
返回值:
成功返回0,出错返回非0。
pthread_cond_broadcast
定义:
int pthread_cond_broadcast(pthread_cond_t *cond);
功能:
唤醒所有等待条件变量的线程。
参数:
cond:条件变量的地址。
返回值:
成功返回0,出错返回非0。
当没有条件变量产生时,调用pthread_cond_wait会导致线程阻塞,同时会将锁解锁,直到条件变量产生时,线程结束阻塞,同时上锁。pthread_cond_signal只能唤醒一个线程,是一对一的关系。而pthread_cond_broadcast可以唤醒所有等待该条件变量的线程,是一对多的关系。
示例代码:
主线程负责从终端输入金额,子线程循环从输入的金额中扣款,每秒扣100元,扣完又调用主线程输入金额。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t lock;
pthread_cond_t cond1;
pthread_cond_t cond2;
int balance = 0;
int flag = 0;
void *take_thread(void *arg)
{
while (1)
{
pthread_mutex_lock(&lock);
if (balance < 100)
{
printf("余额不足100,无法取出\n");
pthread_cond_signal(&cond2);
pthread_cond_wait(&cond1, &lock);
}
balance -= 100;
printf("成功取出:100\n");
printf("余额:%d\n", balance);
sleep(1);
pthread_mutex_unlock(&lock);
}
return NULL;
}
int main(int argc, char const *argv[])
{
pthread_t tid;
if (pthread_create(&tid, NULL, take_thread, NULL) != 0)
{
perror("thread create error");
return -1;
}
if (pthread_mutex_init(&lock, NULL) != 0)
{
perror("lock init error");
return -1;
}
if (pthread_cond_init(&cond1, NULL) != 0)
{
perror("cond1 init error");
return -1;
}
if (pthread_cond_init(&cond2, NULL) != 0)
{
perror("cond2 init error");
return -1;
}
while (1)
{
pthread_mutex_lock(&lock);
if (balance >= 100)
pthread_cond_wait(&cond2, &lock);
printf("请输入金额:");
scanf("%d", &balance);
if (balance >= 100)
pthread_cond_signal(&cond1);
pthread_mutex_unlock(&lock);
}
return 0;
}
由于CPU对线程的调度是随机的,可能输入大于100的金额后会再次执行主线程,所以主线程先要判断金额大于100的话就阻塞,等待子线程扣完款发出条件变量cond2后才可以再次输入。子线程首先需要判断金额是否大于100,小于100的话就向主线程发出条件变量并且阻塞自己,等待主线程输入完且发出条件变量cond1后才可继续执行。
运行结果: