Linux基础——线程同步之互斥锁

1、线程同步的必要性

  1. 共享资源,多个线程都可对共享资源操作
  2. 线程操作共享资源的先后顺序不确定
  3. 处理器对存储器的操作一般不是原子操作

      比如下面一个例子:两个线程同时对一个变量操作(假如对一个变量i进行加1),有可能就会出现数据混乱(即每线程对i+1但是结果不是真正的结果),注意:线程是并发的,出现这种情况很正常。下面写个例子演示一下。

#include  
#include  
#include 
#define NLOOP 5000 	//这里我让每个线程加5000次
int counter;		//定义一个全局变量来让线程自加
//线程执行函数
void *fun(void *arg) 
{ 
	int i = 0;
	int val = 0;
	int ID = 0;
	
	ID = (int)arg;
	//每个线程加个5000次
	for (i = 0; i < NLOOP; i++) 
	{ 
		val = counter; 
		printf("我是第%d个线程: %d\n", ID+1, val + 1); 
		counter = val + 1; 
	}
	return NULL;
}
int main(void)
{
	//建立两个线程ID
	pthread_t tidA, tidB;
 	
 	//创建线程,让这两个线程执行fun函数
    	pthread_create(&tidA, NULL, &fun, NULL);
    	pthread_create(&tidB, NULL, &fun, NULL);
    	
    	//回收进程
    	pthread_join(tidA, NULL);
    	pthread_join(tidB, NULL);
    	return 0;
}

      当我们把这段函数放到虚拟机中运行,最后的结果就可能会是10000的结果,或者少于10000 的结果,但是,按照正常来说应该要是10000,而少于10000的结果就是数据混乱的结果。
注意:编译这个文件的时候要加上-lpthread,比如gcc test.c -o test -lpthread

2、互斥锁

2.1、概念(简单比喻)

      互斥锁我们可以想象成在超市的保险柜(进商场前存放东西的柜子)。然后把线程比喻成一个人,你进超市的时候,你就要先找到没有锁住的柜子,把柜子锁住(表示你正在使用),这样其他的人(其他的线程)就无法再使用这个柜子了,如果想要使用该柜子就需要你解锁,别人才可以使用。

2.2、临界区

      保证在某一时刻只有一个线程能访问数据的简便办法。在任意时刻只允许一个线程对共 享资源进行访问。如果有多个线程试图同时访问临界区,那么 在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释 放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。(你可以这么认为,这临界区就是我上面说的柜子)。
注意:这个临界区要尽可能的小!太大会影响程序的性能!

2.3、互斥锁相关的API函数

  1. pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  • 作用:创建一个静态的互斥锁
  • 解析:pthread_mutex_t是一个结构体,PTHREAD_MUTEX_INITIALIZER是一个常量,这样创建出来的额互斥锁是默认属性的互斥锁。
  1. int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
  • 作用:初始化一个互斥锁,常用于动态的互斥锁(就是属性可换的那种)
  • 解析:你需要先创建一个互斥锁,参数1就是要传一个互斥锁的地址,参数二就是要出入互斥锁的类型,如果位NULL就是默认属性的互斥锁(详细见2.4),一般常用的就是默认类型的互斥锁。
  1. int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • 作用:销毁互斥锁
  • 解析:参数1:传入要销毁的互斥锁地址。该函数销毁后,可用 pthread_mutex_init再重新定义。
  1. int pthread_mutex_lock(pthread_mutex_t *mutex)
  • 作用:加锁(相当于我上面说的给柜子上锁,防止其他线程使用)
  • 解析:参数1:互斥锁地址。该函数使用后,其他线程要是想在访问共享内存的资源就会被挂起(阻塞等待)。
  1. int pthread_mutex_trylock(pthread_mutex_t *mutex);
  • 作用:试图加锁
  • 解析:参数1:互斥锁地址。该函数和pthread_mutex_lock的功能相似,但是他试图去加锁,如果这个锁被已经其他线程上锁的话,我这个线程不会阻塞(不会等待其他线程使用完),直接返回一个错误编码,如果我这个线程加锁成功,就返回0;
  1. int pthread_mutex_unlock(pthread_mutex_t *mutex);
  • 作用:解锁(使用完了,让其他线程使用)
  • 解析:参数1:互斥锁地址。该函数使用后,就相当于我这个保险柜用完了,可以被后面的线程的抢占了。

2.4、互斥锁属性(不重要)

注意:常用的就是默认属性,就是pthread_mutex_init函数的第二个参数传NULL

  • PTHREAD_MUTEX_TIMED_NP,普通锁(默认的锁,传NULL就是这属性)。当一个线程加锁以后,其他的线程就阻塞(等待前面一个线程使用完),并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
  • PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。不常用。
  • PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样保证当不允许多次加锁时不出现最简单情况下的死锁。不怎么常用。
  • PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。不怎么常用。

2.5、死锁

产生原因

  1. 同一个线程在拥有A锁的情况下再次请求获得A锁 ,
    解决方法:避免两次加锁
  2. 线程一拥有A锁,请求获得B锁;线程二拥有B锁,请求获得A锁
    解决方法:使用pthread_mutex_trylock,或者某个线程先放手

造成结果:那个线程也别想用。就会卡住
Linux基础——线程同步之互斥锁_第1张图片Linux基础——线程同步之互斥锁_第2张图片

2.6、示例代码

在下面两个示例代码中,C文件建立静态互斥锁,C++那份代码采用动态建立互斥锁。

2.6.1、基于C的示例代码

#include 
#include 
#include 

#define NLOOP 5000	//用于每个线程加5000次
int counter; 		//建立一个全局变量给线程操作

//设计一个全局的锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void *doit(void *vptr)
{
	int i, val;
 	//谁要操作全局资源,谁拿锁
 	for (i = 0; i < NLOOP; i++) 
	{
  		//拿锁
  		pthread_mutex_lock(&mutex);
  
  		//直到解锁,都是临界区
        	val = counter;
        	printf("%x: %d\n", (unsigned int)pthread_self(), val + 1);
        	counter = val + 1;
        	
  		//解锁
  		pthread_mutex_unlock(&mutex);
        }
        return NULL;
}
int main(void)
{
    	pthread_t tidA, tidB;
    
    	//建立线程
    	pthread_create(&tidA, NULL, &doit, NULL);
    	pthread_create(&tidB, NULL, &doit, NULL);
	
	//回收线程
    	pthread_join(tidA, NULL);
    	pthread_join(tidB, NULL);
    	return 0;
}

2.6.2、基于C++的示例代码

头文件

#ifndef BASETHRED_MUTEX_H
#define BASETHRED_MUTEX_H
#include 
#include 
#include 
class CBaseThread_mutex
{
public:
 	CBaseThread_mutex();
 	~CBaseThread_mutex();

	 bool lock();	//加锁
	 bool unlock();	//解锁
	 bool trylock(); //尝试加锁
protected:
 	pthread_mutex_t m_mutex; //互斥锁
 }	
 #define endif

cpp文件

#include "CBase_pthread_mutex.h"
//构造函数建立互斥锁
CBaseThread_mutex::CBaseThread_mutex()
{
	 //互斥量的初始化
	 if(pthread_mutex_init(&m_mutex,NULL)!=0)
	 {
	  	perror("mutex_init error:");
	 }
}

//析构函数销毁互斥锁
CBaseThread_mutex::~CBaseThread_mutex()
{
 	//互斥量的销毁
 	if(pthread_mutex_destroy(&m_mutex)!=0)
 	{
  		perror("mutex_destroy error:");
 	}
}

//拿锁函数
bool CBaseThread_mutex::lock()
{
 	int ret = pthread_mutex_lock(&m_mutex);
 	if(ret != 0)
 	{
  		perror("mutex_lock error:");
 	}
 	return ret ==0 ? true:false
}

//解锁函数
bool CBaseThread_mutex::unlock()
{
 	int ret = pthread_mutex_unlock(&m_mutex);
 	if(ret != 0)
 	{
  		perror("mutex_unlock error:");
 	}
 	return ret ==0 ? true:false
 
}

//试图拿锁函数
bool CBaseThread_mutex::trylock()
{
 	int ret = pthread_mutex_trylock(&m_mutex);
 	if(ret != 0)
 	{
  		perror("mutex_lock error:");
 	}
 	return ret ==0 ? true:false
}

你可能感兴趣的:(Linux)