互斥锁
线程的主要优势在于能够通过全局变量来共享信息。这种便捷的共享是有代价的:必须确保多个线程不会同时修改同一变量,或者某一线程不会读取正由其他线程修改的变量。
互斥量可以保护对共享变量的访问。
a++需要执行3个步骤:
1.取a的值
2.计算a+1
3.a+1赋值给a
某一时刻,全局变量a的值为5,线程A和线程B中都要执行a++,如果线程A在执行a+1赋值给a之前,这时线程B执行了a++,线程B取到a的值仍是5,这时,线程A和B赋给a的值都是6。值为5的变量a经过两次a++结果却是6,显然出现了错误。
多线程竞争操作共享变量的这段代码叫做临界区。多个进程同时操作临界区会产生错误所以这段代码应该互斥,当一个线程执行临界区时,应该阻止其他线程进入临界区。
为避免线程更新共享变量时出现问题,可以使用互斥量(mutex是mutual exclusion的缩写)来确保同时仅有一个线程可以访问某项共享资源。
互斥量的作用类似于一个“锁”,当一个线程访问共享资源时,它必须先尝试获取互斥量的锁。如果互斥量当前没有被其他线程占用,那么该线程将成功获取锁,并可以安全地访问共享资源;如果互斥量已经被其他线程占用,那么该线程将被阻塞,直到互斥量的锁被释放。
互斥量有两种状态:已锁定(locked)和未锁定(unlocked)。至多只有一个线程可以锁定该互斥量,试图对已经锁定的某一互斥量再次加锁将会阻塞线程。一旦线程锁定互斥量,随即成为该互斥量的所有者,只有所有者才能给互斥量解锁。
pthread_mutex_init 函数
函数描述:
初始化一个互斥量
函数原型:
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *mutexattr);
函数参数:
●mutex:指向互斥量的指针,下面几个函数都有该参数,不一一介绍
●mutexattr:指向定义互斥量属性的指针,取默认值传NULL
函数返回值:
●成功返回0
●失败返回错误号
pthread_mutex_lock和pthread_mutex_unlock函数
函数描述:
给互斥量加锁和解锁,解锁的函数在解锁的同时唤醒阻塞在该互斥量上的线程,默认先阻塞的先唤醒。
函数原型:
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
函数返回值:
●成功返回0
●出现错误返回错误号。加锁不成功,线程阻塞
pthread_mutex_destroy函数
函数描述:
销毁一个互斥量
函数原型:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
函数返回值:
●成功返回0
●失败返回错误号
对这段代码加上相同的锁。
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<stdlib.h>
#define N 10000
int a = 0;
pthread_mutex_t mtx;
void* thread1(void* arg) {
for(int i = 0; i < N; i++){
pthread_mutex_lock(&mtx);
a++;
pthread_mutex_unlock(&mtx);
}
return NULL;
}
int main(int argc, char* argv[])
{
pthread_mutex_init(&mtx, NULL);
pthread_t tid;
pthread_create(&tid, NULL, thread1, NULL);
for(int i = 0; i < N; i++){
pthread_mutex_lock(&mtx);
a++;
pthread_mutex_unlock(&mtx);
}
pthread_join(tid, NULL);
printf("a = %d\n", a);
pthread_mutex_destroy(&mtx);
return 0;
}
pthread_mutex_trylock函数
函数描述:
尝试给互斥量加锁,加锁不成功直接返回错误号(EBUSY),不会阻塞,其他与pthread_mutex_lock相同。
#define N 10000
int a = 0;
pthread_mutex_t mtx;
void* thread1(void* arg) {
sleep(1);
int ret = pthread_mutex_trylock(&mtx);
if(ret != 0 && ret == EBUSY){
printf("非阻塞加锁, 当前锁被占用\n");
}
}
int main(int argc, char* argv[])
{
pthread_mutex_init(&mtx, NULL);
pthread_t tid;
pthread_create(&tid, NULL, thread1, NULL);
pthread_mutex_lock(&mtx);
pthread_exit(NULL);
}
练习
模拟选座系统,有10个空座,12个用户(线程)同时选座,系统从空座中随机选择一个,打印出当前用户的序号和选中座位序号(线程创建顺序就是用户序号顺序)。
输出正确结果应为:10个用户成功,2个失败,没有重复座位
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<errno.h>
#include<stdlib.h>
#include<time.h>
pthread_mutex_t mtx;
int empty_seats_num = 10;//初始空座10个
int empty_seats[10];
//每次得到新的随机数
int choose_seat() {
if(empty_seats_num == 0){
return -1;
}
int seat_insex = rand() % empty_seats_num;//空位下标
int seat_number = empty_seats[seat_insex];//根据下标把座位取出来
//把最后的10放进来
empty_seats[seat_insex] = empty_seats[empty_seats_num - 1];
empty_seats_num--;
return seat_number;//得到座位数
}
void* thread1(void* arg) {
pthread_mutex_lock(&mtx);//加锁
int seat_number = choose_seat();
pthread_mutex_unlock(&mtx);//解锁
if(seat_number != -1) {
printf("用户 %d 成功选得座位数为 %d\n", *(int*)arg, seat_number);
} else {
printf("用户 %d 选座失败,座位已售空\n", *(int*)arg);
}
}
int main(int argc, char* argv[])
{
srand(time(NULL));
for (int i = 0; i < 10; i++){
empty_seats[i] = i+1;//循环遍历输入十个数
}
pthread_mutex_init(&mtx, NULL);//初始化一个互斥量
//创建12个线程的线程id的数组
pthread_t tids[12];
for (int i = 0; i < 12; i++){
int* user_number = (int*)malloc(sizeof(int));
* user_number = i + 1;
pthread_create(&tids[i], NULL, thread1, user_number);
}
//等待指定线程终止并回收
for (int i = 0; i < 12; i++){
pthread_join(tids[i], NULL);
}
//销毁
pthread_mutex_destroy(&mtx);
return 0;
}