1. 互斥量
在Linux 下线程使用的都是局部变量, 而我们知道, 每一个线程都独立拥有自己的一个栈, 而这些局部便令就在栈中,而线程的创建就是为了实现通信, 此时线程之间无法共享这些变量
为了使得线程之间能够共享数据, 一次我们可以创建一个全局变量, 此时线程都在进程内部共享一个地址空间, 因此个线程之间就可以看到这个全局变量了
但是问题又来了, 创建了全局变量, 线程之间其实看到了一份公共资源, 而此时一个线程之间由于共同访问这个局部变量很有可能造成线程之间的不安全, 为了使得线程之间能够正确访问, 我们就引入了互斥量.我们规定,当代码进入临界区执行的时候, 不允许其他线程进入该临界区. 当有多个线程要求执行临界区的代码时,此时如果临界区没有如何线程的时候, 操作系统只允许一个线程进入该临界区, 而其他的线程则必须在临界区外等待, 直到进入临界区的线程走出临界区, 并且释放互斥量.如果线程不在临界区内, 该线程不能组织其他线程进入临界区
2.互斥量相关接口
1.初始化互斥量
pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t* restict attr);参数:mutex:要初始化的互斥量attr: NULL
2.销毁互斥量
int pthread_mutex_destroy(pthread_mutex_t* mutex);
3.互斥量的加锁
int pthread_mutex_lock(pthrea_mutex_t* mutex);
4.互斥量的解锁
int pthread_mutex_unlock(pthread_mutex_t* mutex);
注意, 互斥量处于互斥状态时, 加锁函数会将该互斥量锁定, 同时返回成功, 当发起加锁函数被调用的时候, 其他线程已经锁定互斥量, 或者当好多线程同时申请互斥量的时候, 此时线程之间就在相互竞争这个互斥量,此时, 该函数调用将会陷入阻塞状态,一直等待该互斥量, 直到拥有该互斥量的线程主动释放该互斥锁
#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>int ticket = 100;
pthread_mutex_t mutex;
void* route(void* arg)
{char* id = (char*)arg;while(1){//pthread_mutex_lock(&mutex);if(ticket > 0){usleep(10000);printf("%s sells ticket:%d\n", id, ticket);ticket--;//pthread_mutex_unlock(&mutex);}else{//pthread_mutex_unlock(&mutex);break;}}
}
int main()
{pthread_t t1, t2, t3, t4;pthread_mutex_init(&mutex, NULL);pthread_create(&t1, NULL, route, "thread 1");pthread_create(&t2, NULL, route, "thread 2");pthread_create(&t3, NULL, route, "thread 3");pthread_create(&t4, NULL, route, "thread 4");pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);return 0;
}
由上图可以看出, 由于我们未加互斥锁, 线程对数据的不正确操作造成了数据的错误,当我我们在线程进入临界区的时候临界资源加上互斥锁, 而在线程离开临界区的时候对互斥量进行释放, 此时就不会出现数据错误的现象了
3. 条件变量
当一个线程互斥访问某个变量时, 它可能发现在其他线程改变状态的之前它什么也不能做, 例如一个线程访问队列时, 它发现这个队列为空, 此时,它只能等待, 直到其他线程将结点放到该队列.这时, 为了使得各个线程之间能够同步的访问这个变量,此时就需要有一个变量, 该变量必须要符合某个条件时, 线程才能访问这个变量.
4. 相关接口
1. 初始化
int pthread_cond_init(pthread_cond_t* restrict cond, const pthread_condattr_t* restrict attr);cond: 表示要初始化的条件变量attr: NULL
2. 销毁
int pthread_cond_destroy(pthread_cond_t* cond)
3. 等待条件满足
int pthread_cond_wait(pthread_cond_t* restrict cond, pthread_mutex_t* restrict mutex);cond: 如果条件不满足, 就在条件变量上等待mutex: 互斥量, 表名线程退出时必须释放哪一个锁
4. 唤醒
int pthread_cond_broadcast(pthread_cond_t* cond);用来唤醒一群线程
int pthread_cond_signal(pthread_cond_t* cond);用来唤醒某个线程
也许你会问, 既然线程等待, 那就直接等待就好了, 有一个条件变量就可以了, 但是为什么好要有一个互斥量呢?通过上面所述, 我们知道, 条件变量是为了使得线程之间得到同步所采用的一种手段. 当只有一个线程的时候, 条件不满足, 此时线程等待, 一直等待下去, 此时由于只有一个线程, 该线程所等待的条件将始终不会得到满足, 一次线程将会一直等待下去. 所以, 为了能够及时通知等待的线程, 必须有另外的线程对等待的这个条件(共享变量)做出预定的操作, 使得等待的线程所等待的条件得到满足, 此时, 这个条件变量是一个共享变量, 为了使得每一个线程访问该数据的正确性, 此时就必须引入互斥量来保证临界资源的正确性.
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<stdlib.h>
#include<string.h>pthread_cond_t cond;
pthread_mutex_t mutex;void* thread1(void* arg)
{while(1){printf("%s\n", (char*)arg);pthread_cond_wait(&cond, &mutex);printf("活动\n");}
}void* thread2(void* arg)
{while(1){sleep(2);printf("%s\n", (char*)arg);pthread_cond_signal(&cond);}
}
int main()
{pthread_t t1;pthread_t t2;pthread_cond_init(&cond, NULL);pthread_mutex_init(&mutex, NULL);pthread_create(&t1, NULL, thread1, "thread1");pthread_create(&t2, NULL, thread2, "thread2");pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_cond_destroy(&cond);pthread_cond_destroy(&cond);return 0;
}
上面代码是用来创建线程1, 线程2, 同时线程在条件变量上进行等待, 直到有线程将其唤醒, 线程2睡眠一秒, 然后将等待 cond 的线程1唤醒, 此时线程2被唤醒, 它开始执行, 执行完之后,它有开始等待条件变量 cond直到条件变量 cond 满足时再醒来.
通过上图可以看出来, 线程2要活动, 它必须等待条件变量 cond 满足时才能活动, 即线程2 必须在条件变量上等待, 直到线程 2 将其唤醒