一、互斥量
互斥量又叫互斥锁,它是用来确保某一时刻一些数据(比如链表)只会被一个线程访问。
有两种初始化的方式:
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
或者
pthread_mutex_t lock;
pthread_mutex_init(&lock, NULL);
然后就是使用,加锁和解锁:
加锁需要使用pthread_mutex_lock(&lock);(pthread_mutex_trylock(&lock)会尝试加锁,区别是,如果互斥量已经被加锁,使用trylock不会阻塞,而是直接返回EBUSY)
解锁需要用到pthread_mutex_unlock(&lock);
二、条件变量
条件变量也是多线程之间共享数据时用到的。它适用的情况是,线程1改变了数据,达到某一个程度后,线程2才能进行(例如生产者-消费者模式中,只有生产者生产了东西,消费者才能消费)。但是如果让线程2一直阻塞在那边,不断地检查是否它自身能否进行(在生产者-消费者模式中,条件可能是东西是否被生产出来了),很浪费资源。最好的就是,线程2先休眠在那边,不用一直查询条件是否满足,等到线程1这边修改了数据,达到了线程2进行的条件时,通知线程2就可以了。条件变量就是起了这个作用。
类似互斥量,条件变量也有两种初始化方式:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
或者
pthread_cond_t cond;
pthread_cond_init(&cond);
然后就是使用,这里用到的函数是pthread_cond_wait(&cond, &lock);(还有一个可以设置timeout的类似函数)
我一共写了两个代码进行研究,先来第一个:
#include
#include
#include
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int n = 0;
void *wait(void *p)
{
printf("step 1 The start of wait thread\n");
pthread_mutex_lock(&mutex);
while(2 != n)
{
printf("step 2 the condition is false, the thread will sleep");
pthread_cond_wait(&cond, &mutex);
printf("step 3 now n = %d\n", n);
}
pthread_mutex_unlock(&mutex);
return (void *)0;
}
void *signal(void *p)
{
printf("step 1 The start of signal thread\n");
pthread_mutex_lock(&mutex);
sleep(3);
n = 2;
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond);
return (void *)0;
}
int main()
{
pthread id1, id2;
ret = pthread_create(&id1, NULL, wait, NULL);
if(ret)
{
printf("Create fail\n");
return 0;
}
ret = pthread_create(&id2, NULL, signal, NULL);
if(ret)
{
printf("Create fail\n");
return 0;
}
pthread_join(id1, NULL);
pthread_join(id2, NULL);
printf("Main thread finished\n");
return 0;
}
这是一个最简单的wait-signal模型。
wait和signal两个线程里都有对互斥量进行加锁的操作。如果signal先加锁,那么wait进行到step 1就阻塞住了,等signal解锁之后,wait才继续往下执行;如果wait先加锁,那么signal也同样会阻塞。这是互斥量的作用,互斥量加锁阻塞的是一段代码。
但是第二种情况下,wait里面有一个pthread_cond_wait函数,这个函数做了什么事儿呢?
它先把wait进程休眠(而不是阻塞在那边,不用占用CPU资源),然后解锁当前mutex(使得signal进程可以使用mutex),这两步是原子操作。之后wait进程便进入了等待状态,受到signal之后,便又对mutex加锁,函数返回。
这里有几个问题:
1.signal进程发送信号和mutex解锁的先后问题。如果发送signal在前,那么wait唤醒之后要对mutex进行加锁,但是如果此时signal并没有对mutex完成解锁,那么wait又会进入休眠。如果是对mutex解锁在前,那么wait线程收到signal似乎就能正确的对mutex进行加锁了。我实验了发现这两者并没有区别,所以这边还是存疑的。
2.wait线程中判断条件用while而不是if.这边我纠结了很久,因为存在虚假唤醒---即使没有线程调用发送signal或者broadcast,原先调用pthread_cond_wait的函数也有可能返回,这就有点坑爹了。如果哪一次pthread_cond_wait返回时不时因为我主动的发signal,那不就是在判断条件为假的情况下也会执行接下来的代码吗?所以用while来判断,这样的话即使是虚假唤醒,while又会回去检查一下条件是否为真。如果是if, 就会直接往下执行了。while可以确保pthread_cond_wait之前和之后(唤醒之后)条件都是我需要的。
3.进程被唤醒之后,是从pthread_cond_wait接着往下执行。
4.pthread_join函数,可以把主线程阻塞在那边,等子线程都执行完才会执行主线程。如果不加这个,子线程还没有执行完,主线程就退出了。
我另一个看的代码是迅雷的面试题,开启三个线程,使得这三个线程交替打印ABCABCABC,感兴趣的可以去搜来看看。
终于写完了,这是我的第一篇博客,主要是做个笔记,为了以后复习方便,也为了面试做准备。以后还会继续写。
看到这边的人应该能感觉到,我是一个技术小白,希望大家有什么意见和建议都能告诉我,欢迎讨论。