1、相关背景知识
临界资源 | 多线程、多执行流共享的资源,就叫做临界资源 |
临界区 | 每个线程内部,访问临界资源的代码 |
互斥 | 在任何时刻,保证有且只有一个执行流进入临界区,访问临界资源,对临界资源起到保护作用 |
原子性 | 不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么没开始。(只要开始,必须完成了才能中断) |
2、多线程工作的问题(抢票样例)
1、首先存在初始为100的票数tickets。
2、然后新建4个线程,进行抢票。
3、为了让tickets成为临界资源,将tickets初始为全局的。
int tickets = 100; // 票数
void Job(const std::string &name)
{
while (true)
{
if (tickets > 0)
{
/*抢票*/
usleep(1000); // 1ms -> 抢票时间
printf("who : %s , get a ticket , remain tickets : %d\n", name.c_str(), tickets); // 抢票
tickets--;
}
else
{
break;
}
}
}
int main()
{
Thread t1("thread-1", Job);
Thread t2("thread-2", Job);
Thread t3("thread-3", Job);
Thread t4("thread-4", Job);
t1.Start();
t2.Start();
t3.Start();
t4.Start();
t1.Join();
t2.Join();
t3.Join();
t4.Join();
return 0;
}
查看运行结果:
本来当remain tickets为0的时候,就不应该进入抢票逻辑了。
但是输出结果明显不对劲,票数都变为负数了,还在抢票。
为什么会出现这种情况呢?
多个线程并发的操作临界资源,就会带来一些问题。
因此,多个线程的临界区代码,必须具有互斥行为。
为了保证这个互斥行为,就引出了互斥量(即,加锁)。
3、锁
3.1、认识锁和接口
pthread_mutex_t | pthread库提供的互斥锁类型 |
PTHREAD_MUTEX_INITIALIZER | 如果锁是全局的或者静态的,可以直接mutex = PTHREAD_MUTEX_INITIALIZER进行初始化,并且最后不需要销毁 |
int pthread_mutex_init(pthread_mutex *restrict mutex, const pthread_mutexattr_t *restrict attr); | 初始化锁。 restrict mutex:就是需要初始化的锁。 restrict attr:表示设置锁的属性,一般置空即可。 |
int pthread_mutex_destroy(pthread_mutex_t *mutex); | 销毁锁。 用了init函数,才用destroy。 如果采用全局锁初始化的方式,不用destroy。 |
int pthread_mutex_lock(pthread_mutex_t *mutex); | 加锁。当锁正在被占用的时候,调用lock函数的线程,阻塞等待锁被释放,然后继续竞争。 |
int pthread_mutex_trylock(pthread_mutex_t *mutex); | 加锁。当锁正在被占用的时候,调用trylock函数的线程,争夺锁失败,直接返回一个指示值,告诉使用者是否争夺成功。 |
int pthread_mutex_unlock(pthread_mutex_t *mutex); | 解锁。 |
3.2、加锁的时机/原则
1、加锁的力度(范围),一定要小。因为在临界区这,一个线程执行,其它线程全部阻塞。如果一直阻塞,导致效率降低。 |
2、任何时刻,访问临界区的线程,必须先要申请锁。否则会出现数据不一致问题。 |
3、加锁的前提,必须是线程都能看到这一把锁。因此,这把锁也是临界资源。但是不可能加锁之前又要加锁吧,这就套娃了。所以,加锁过程,是原子的。 |
4、如果线程申请锁失败,就阻塞原地,等着锁被释放,然后继续竞争。 反之,申请锁成功,就继续执行后续代码。 |
5、申请锁成功的线程,在访问临界区的时候,是可以被调度走的。 但是锁不会被释放,其它线程依然进不去。 即:对于其它线程来说,占据锁的线程,访问临界区,在它们看来是原子的。 |
3.3、对比不同加锁时机导致的不同现象(抢票样例)
3.3.1、while后加锁,if和else后都释放锁
void Job(const std::string &name)
{
while (true)
{
pthread_mutex_lock(&g_mutex); // 加锁
if (tickets > 0)
{