目录
同步与互斥
互斥锁(同步)
条件变量(同步)
读写锁(同步)
自旋锁(同步)
信号量(同步与互斥)
由于近期在做ppp拨号的项目时,涉及到多个线程的同步问题,例如我的ppp拨号线程依赖于gsmd线程先完成sim卡的状态检测以及信号强弱的检测,等这一系列的操作完成之后才会进行ppp拨号的操作,自己也因此再次学习了Liunx下线程同步和互斥的实现方法。
现代操作系统基本都是多任务操作系统,即同时有大量可调度实体在运行。在多任务操作系统中,同时运行的多个任务可能:
【同步】:
是指散步在不同任务之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。最基本的场景就是:两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。比如 A 任务的运行依赖于 B 任务产生的数据。
【互斥】:
是指散步在不同任务之间的若干程序片断,当某个任务运行其中一个程序片段时,其它任务就不能运行它们之中的任一程序片段,只能等到该任务运行完这个程序片段后才可以运行。最基本的场景就是:一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。
在多任务操作系统中,同时运行的多个任务可能都需要使用同一种资源。这个过程有点类似于,公司部门里,我在使用着打印机打印东西的同时(还没有打印完),别人刚好也在此刻使用打印机打印东西,如果不做任何处理的话,打印出来的东西肯定是错乱的。
在线程里也有这么一把锁——互斥锁(mutex),互斥锁是一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即上锁( lock )和解锁( unlock )。
【互斥锁的特点】:
1. 原子性:把一个互斥量锁定为一个原子操作,这意味着操作系统(或pthread函数库)保证了如果一个线程锁定了一个互斥量,没有其他线程在同一时间可以成功锁定这个互斥量;
2. 唯一性:如果一个线程锁定了一个互斥量,在它解除锁定之前,没有其他线程可以锁定这个互斥量;
3. 非繁忙等待:如果一个线程已经锁定了一个互斥量,第二个线程又试图去锁定这个互斥量,则第二个线程将被挂起(不占用任何cpu资源),直到第一个线程解除对这个互斥量的锁定为止,第二个线程则被唤醒并继续执行,同时锁定这个互斥量。
【互斥锁的操作流程如下】:
1. 在访问共享资源后临界区域前,对互斥锁进行加锁;
2. 在访问完成后释放互斥锁导上的锁。在访问完成后释放互斥锁导上的锁;
3. 对互斥锁进行加锁后,任何其他试图再次对互斥锁加锁的线程将会被阻塞,直到锁被释放。对互斥锁进行加锁后,任何其他试图再次对互斥锁加锁的线程将会被阻塞,直到锁被释放。
#include
#include
// 初始化一个互斥锁。
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr);
// 对互斥锁上锁,若互斥锁已经上锁,则调用者一直阻塞,
// 直到互斥锁解锁后再上锁。
int pthread_mutex_lock(pthread_mutex_t *mutex);
// 调用该函数时,若互斥锁未加锁,则上锁,返回 0;
// 若互斥锁已加锁,则函数直接返回失败,即 EBUSY。
int pthread_mutex_trylock(pthread_mutex_t *mutex);
// 当线程试图获取一个已加锁的互斥量时,pthread_mutex_timedlock 互斥量
// 原语允许绑定线程阻塞时间。即非阻塞加锁互斥量。
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,const struct timespec *restrict abs_timeout);
// 对指定的互斥锁解锁。
int pthread_mutex_unlock(pthread_mutex_t *mutex);
// 销毁指定的一个互斥锁。互斥锁在使用完毕后,
// 必须要对互斥锁进行销毁,以释放资源。
int pthread_mutex_destroy(pthread_mutex_t *mutex);
与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直 到某特殊情况发生为止。通常条件变量和互斥锁同时使用。
条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步 的一种机制,主要包括两个动作:
一个线程等待"条件变量的条件成立"而挂起;
另一个线程使 “条件成立”(给出条件成立信号)。
【原理】:
条件的检测是在互斥锁的保护下进行的。线程在改变条件状态之前必须首先锁住互斥量。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量 可以被用来实现这两进程间的线程同步。
【条件变量的操作流程如下】:
1. 初始化:init()或者pthread_cond_tcond=PTHREAD_COND_INITIALIER
;属性置为NULL;
2. 等待条件成立:pthread_wait,pthread_timewait.wait()释放锁,并阻塞等待条件变量为真 timewait()设置等待时间,仍未signal,返回ETIMEOUT(加锁保证只有一个线程wait);
3. 激活条件变量:pthread_cond_signal,pthread_cond_broadcast(激活所有等待线程)
4. 清除条件变量:destroy;无线程等待,否则返回EBUSY清除条件变量:destroy;无线程等待,否则返回EBUSY。
#include
// 初始化条件变量
int pthread_cond_init(pthread_cond_t *cond,
pthread_condattr_t *cond_attr);
// 阻塞等待
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
// 超时等待
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,
const timespec *abstime);
// 解除所有线程的阻塞
int pthread_cond_destroy(pthread_cond_t *cond);
// 至少唤醒一个等待该条件的线程
int pthread_cond_signal(pthread_cond_t *cond);
// 唤醒等待该条件的所有线程
int pthread_cond_broadcast(pthread_cond_t *cond);
读写锁与互斥量类似,不过读写锁允许更改的并行性,也叫共享互斥锁。互斥量要么是锁住状态,要么就是不加锁状态,而且一次只有一个线程可以对其加锁。读写锁可以有3种状态:
读模式下加锁状态、
写模式加锁状态、
不加锁状态。
一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁(允许多个线程读但只允许一个线程写)
【读写锁的特点】:
【读写锁的规则】:
如果某线程申请了读锁,其它线程可以再申请读锁,但不能申请写锁;
如果某线程申请了写锁,其它线程不能申请读锁,也不能申请写锁。
读写锁适合于对数据结构的读次数比写次数多得多的情况。
#include
// 初始化读写锁
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);
// 申请读锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock );
// 申请写锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock );
// 尝试以非阻塞的方式来在读写锁上获取写锁,
// 如果有任何的读者或写者持有该锁,则立即失败返回。
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
// 解锁
int pthread_rwlock_unlock (pthread_rwlock_t *rwlock);
// 销毁读写锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
自旋锁与互斥量功能一样,唯一一点不同的就是互斥量阻塞后休眠让出cpu,而自旋锁阻塞后不会让出cpu,会一直忙等待,直到得到锁。
自旋锁在用户态使用的比较少,在内核使用的比较多!自旋锁的使用场景:锁的持有时间比较短,或者说小于2次上下文切换的时间。
自旋锁在用户态的函数接口和互斥量一样,把pthread_mutex_xxx()中mutex换成spin,如:pthread_spin_init()。
信号量广泛用于进程或线程间的同步和互斥,信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。
编程时可根据操作信号量值的结果判断是否对公共资源具有访问的权限,当信号量值大于 0 时,则可以访问,否则将阻塞。PV 原语是对信号量的操作,一次 P 操作使信号量减1,一次 V 操作使信号量加1。(操作系统的PV操作)
#include
// 初始化信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
// 信号量 P 操作(减 1)
int sem_wait(sem_t *sem);
// 以非阻塞的方式来对信号量进行减 1 操作
int sem_trywait(sem_t *sem);
// 信号量 V 操作(加 1)
int sem_post(sem_t *sem);
// 获取信号量的值
int sem_getvalue(sem_t *sem, int *sval);
// 销毁信号量
int sem_destroy(sem_t *sem);
信号量用于同步:
/ 信号量用于同步实例
#include
#include
#include
#include
sem_t g_sem_g;
sem_t g_sem_p;
int g_num = 1024;
void *pthread_g(void *arg) //此线程改变字符ch的值
{
while(1)
{
sem_wait(&sem_g);
g_num++;
sleep(1);
sem_post(&sem_p);
}
}
void *pthread_p(void *arg) //此线程打印ch的值
{
while(1)
{
sem_wait(&sem_p);
printf("%d",g_num);
fflush(stdout);
sem_post(&sem_g);
}
}
int main(int argc, char *argv[])
{
pthread_t tid1,tid2;
sem_init(&sem_g, 0, 0); // 初始化信号量为0
sem_init(&sem_p, 0, 1); // 初始化信号量为1
// 创建两个线程
pthread_create(&tid1, NULL, pthread_g, NULL);
pthread_create(&tid2, NULL, pthread_p, NULL);
// 回收线程
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}
信号量用于互斥:
// 信号量用于互斥实例
#include
#include
#include
#include
sem_t g_sem; //信号量
void printer(char *str)
{
sem_wait(&sem);//减一,p操作
while(*str) // 输出字符串(如果不用互斥,此处可能会被其他线程入侵)
{
putchar(*str);
fflush(stdout);
str++;
sleep(1);
}
printf("\n");
sem_post(&sem);//加一,v操作
}
void *thread_fun1(void *arg)
{
char *str1 = "hello";
printer(str1);
}
void *thread_fun2(void *arg)
{
char *str2 = "world";
printer(str2);
}
int main(void)
{
pthread_t tid1, tid2;
sem_init(&sem, 0, 1); //初始化信号量,初始值为 1
//创建 2 个线程
pthread_create(&tid1, NULL, thread_fun1, NULL);
pthread_create(&tid2, NULL, thread_fun2, NULL);
//等待线程结束,回收其资源
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
sem_destroy(&sem); //销毁信号量
return 0;
}