Linux多线程4-1_互斥量

一、为什么要使用互斥量

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;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值