一、为什么要使用互斥量
1、当多个线程共享相同的内存时,需要每一个线程看到相同的视图。当一个线程修改变量时,而其他线程也可以读取或者修改这个变量,就需要对这些线程同步,确保他们不会访问到无效的变量。
2、在变量修改时间多于一个存储器访问周期的处理器结构中,当存储器的读和写这两个周期交叉时,这种潜在的不一致性就会出现。当然这与处理器相关,但是在可移植的程序中并不能对处理器做出任何假设。
二、互斥量的初始化
1、为了让线程访问数据不产生冲突,这要就需要对变量加锁,使得同一时刻只有一个线程可以访问变量。互斥量本质就是锁,访问共享资源前对互斥量加锁,访问完成后解锁。
2、当互斥量加锁以后,其他所有需要访问该互斥量的线程都将阻塞。
3、当互斥量解锁以后,所有因为这个互斥量阻塞的线程都将变为就绪态,第一个获得cpu的线程会获得互斥量,变为运行态,而其他线程会继续变为阻塞,在这种方式下访问互斥量每次只有一个线程能向前执行。
4、互斥量用pthread_mutex_t类型的数据表示,在使用之前需要对互斥量初始化。
1)、如果是动态分配的互斥量,可以调用pthread_mutex_init()函数初始化
2)、如果是静态分配的互斥量,还可以把它置为常量PTHREAD_MUTEX_INITIALIZER
3)、动态分配的互斥量在释放内存之前需要调用pthread_mutex_destroy()
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
三、加锁和解锁
加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
成功返回0,失败返回错误码。如果互斥量已经被锁住,那么会导致该线程阻塞。
int pthread_mutex_trylock(pthread_mutex_t *mutex);
成功返回0,失败返回错误码。如果互斥量已经被锁住,不会导致线程阻塞。
解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
成功返回0,失败返回错误码。如果一个互斥量没有被锁住,那么解锁就会出错 。
四、死锁
1、死锁:线程一直在等待锁,而锁却无法解开
2、如果一个线程对已经占有的互斥量继续加锁,那么他就会陷入死锁状态。
lock mutex--------lock mutex--------阻塞
解除阻塞状态的条件:mutex unlock
mutex unlock的条件: 解除阻塞态
3、程序中使用多个互斥量时,如果一个线程一直占有互斥量A,并且试图加锁互斥量B,但是拥有互斥量B的线程却要加锁互斥量A,这时就会出现死锁。
线程1: lock A成功
线程2 : lock B成功
线程1: lock B失败, 阻塞
线程2: lock A失败, 阻塞
线程1解除阻塞的条件:线程2将B解锁
线程2解除阻塞的条件:线程1将A解锁
4、如何去避免
你可以小心的控制互斥量加锁的顺序来避免死锁,例如所有的线程都在加锁B之前先加锁A,那么这两个互斥量就不会产生死锁了。有的时候程序写的多了互斥量就难以把控,你可以先释放已经占有的锁,然后再加锁其他互斥量。
5、互斥量使用要注意:
1)、访问共享资源时需要加锁
2)、互斥量使用完之后需要销毁
3)、加锁之后一定要解锁
4)、互斥量加锁的范围要小
5)、互斥量的数量应该少
五、互斥量实例
1、程序框图
2、源代码
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <iostream>
#include "include/pthread.h"
#ifndef _WIN64
#pragma comment(lib,".\\lib32\\pthreadVC2.lib")
#else
#pragma comment(lib,".\\lib64\\pthreadVC2.lib")
#endif
/*
*DESCRIPTION: 多线程访问变量产生错误的例子
*/
/*
4、互斥量用pthread_mutex_t类型的数据表示,在使用之前需要对互斥量初始化
1)、如果是动态分配的互斥量,在申请内存(malloc)之后, 可以调用pthread_mutex_init()函数初始化
2)、如果是静态分配的互斥量,还可以把它置为常量PTHREAD_MUTEX_INITIALIZER
3)、动态分配的互斥量在释放内存之前需要调用pthread_mutex_destroy()
*/
struct student {
int id;
int age;
int name;
}stu;
//定义两个全局变量,因为两个线程都要访问
int i;
pthread_mutex_t mutex;
void *thread_fun1(void *arg)
{
while (1)
{
//加锁,对整个结构体访问进行加锁,防止产生错乱
pthread_mutex_lock(&mutex);
stu.id = i;
stu.age = i;
stu.name = i;
i++;
if (stu.id != stu.age || stu.id != stu.name || stu.age != stu.name)
{
printf("thread 1 %d,%d,%d\n", stu.id, stu.age, stu.name);
break;
}
//访问变量完成,需要进行解锁,只有这样其他线程才能访问
pthread_mutex_unlock(&mutex);
}
return(void *)0;
}
void *thread_fun2(void *arg)
{
while (1)
{
//加锁,对整个结构体访问进行加锁,防止产生错乱
pthread_mutex_lock(&mutex);
stu.id = i;
stu.age = i;
stu.name = i;
i++;
if (stu.id != stu.age || stu.id != stu.name || stu.age != stu.name)
{
printf("thread 2 %d,%d,%d\n", stu.id, stu.age, stu.name);
//break;
}
pthread_mutex_unlock(&mutex);
}
return(void *)0;
}
int main()
{
pthread_t tid1, tid2;
int err;
//对互斥量进行初始化,只有初始化过到互斥量才能使用
err = pthread_mutex_init(&mutex, NULL);
if (err != 0)
{
printf("init mutex failed\n");
return 0;
}
//创造新线程
err = pthread_create(&tid1, NULL, thread_fun1, NULL);
if (err != 0)
{
printf("create new thread failed\n");
return 0;
}
//创造新线程
err = pthread_create(&tid2, NULL, thread_fun2, NULL);
if (err != 0)
{
printf("create new thread failed\n");
return 0;
}
//等待新线程运行结束
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}
作业:使用多线程对一个队列进行增加和减少,增加操作是一个线程,删除操作是一个线程
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <iostream>
#include <queue>
#include "include/pthread.h"
#ifndef _WIN64
#pragma comment(lib,".\\lib32\\pthreadVC2.lib")
#else
#pragma comment(lib,".\\lib64\\pthreadVC2.lib")
#endif
/*
4、互斥量用pthread_mutex_t类型的数据表示,在使用之前需要对互斥量初始化
1)、如果是动态分配的互斥量,在申请内存(malloc)之后, 可以调用pthread_mutex_init()函数初始化
2)、如果是静态分配的互斥量,不需要调用 pthread_mutex_init() 函数。还可以把它置为常量PTHREAD_MUTEX_INITIALIZER
3)、动态分配的互斥量在释放内存之前需要调用pthread_mutex_destroy()
*/
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
struct queue {
int len;
int write_pos;
int read_pos;
int data[50];
};
struct queue *queue_init()
{
struct queue *que;
//申请内存
que = (struct queue *)malloc(sizeof(struct queue));
if (que == NULL)
{
printf("malloc failed\n");
return NULL ;
}
//初始化
que->len = 0;
que->write_pos = 0;
que->read_pos = 0;
return que;
}
void queue_destroy(struct queue *que)
{
//销毁互斥量和que
pthread_mutex_destroy(&mutex);
free(que);
}
void *queue_add(void *arg)
{
struct queue *que = (struct queue *)arg;
int buf = 0;
while (buf < 50)
{
pthread_mutex_lock(&mutex);
que->data[que->write_pos] = buf;
que->write_pos++;
que->len++;
buf++;
printf("write data %d to queue\n", que->data[que->write_pos - 1]);
pthread_mutex_unlock(&mutex);
Sleep(1);
}
return (void*)0;
}
void *queue_del(void *arg)
{
struct queue *que = (struct queue *)arg;
int buf = 0;
while (1)
{
Sleep(2);
pthread_mutex_lock(&mutex);
buf = que->data[que->read_pos];
que->read_pos++;
if (que->len-- == 0)
{
printf("queue is empty\n");
return (void*)0;
}
buf++;
printf("read data %d from queue\n", que->data[que->read_pos - 1]);
pthread_mutex_unlock(&mutex);
}
}
int main()
{
pthread_t tid1, tid2;
int err;
struct queue *que;
//队列和锁都要初始化
que = queue_init();
//如果是静态分配的互斥量,不需要调用 pthread_mutex_init() 函数。
//还可以把它置为常量PTHREAD_MUTEX_INITIALIZER
/*err = pthread_mutex_init(&mutex, NULL);
if (err)
{
printf("mutex init failed\n");
free(que);
return 0;
}
*/
err = pthread_create(&tid1, NULL, queue_add, (void *)que);
if (err)
{
printf("create add thread failed\n");
queue_destroy(que);
return 0;
}
err = pthread_create(&tid2, NULL, queue_del, (void *)que);
if (err)
{
printf("create del thread failed\n");
queue_destroy(que);
return 0;
}
//等待增加和删除操作完成
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
//销毁
queue_destroy(que);
return 0;
}