Linux | 线程同步--信号量、互斥锁和条件变量

一、线程

线程是程序中完成一个独立任务的完整执行序列,即一个可调度的实体。

Linux线程库:

Linux上两个最有名的线程库是 LinuxThreads 和NPTL。

用户可以使用如下命令来查看当前系统上所使用的的线程库

getconf GNU_LIBPTHREAD_VERSION

(我查了下自己的,发现我系统上使用的线程库是NPTL2.12 )

二、线程同步 

多线程程序要考虑同步问题。有问题当然就有解决的办法!!

下面我们讨论3种专门用于线程同步的机制:信号量、互斥量和条件变量。

1. 信号量

是一个线程同步结构,用于在线程间传递信号,以避免出现信号丢失。

常用的信号量函数是下面5个:

#include 
//初始化一个未命名的信号量
int sem_init(sem_t *sem,int shared,unsigned int value); 

//销毁信号量,以释放其占用的内核资源
int sem_destroy(sem_t *sem); 

//以原子操作的方式将信号量的值 -1。如果信号量的值为0,则该函数阻塞,直到该信号量具有非0值。
int sem_wait(sem_t *sem); 

//与sem_wait函数类似,不过它始终立即返回,而不论被操作的信号量是否有非0值,相当于sem_wait的非阻塞版本 
int sem_trywait(sem_t *sem);  

//以原子操作的方式将信号量的值 +1。当信号量的值大于0时,其他正在调用sem_wait等待信号量的线程将被唤醒。
int sem_post(sem_t *sem);  

这些函数的第一个参数 sem 指向被操作的信号量。 

原子操作:是指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何context switch(切换到另一个线程)

2. 互斥锁

互斥锁(也称互斥量)可以用来保护关键代码段,以确保其独占式的访问。

互斥锁是只能在线程之间使用的一种控制临界资源访问的机制,如果一个线程要访问临界资源,则必须先加锁,用完之后解锁。这样,在一个线程访问临界资源的过程中,其他线程加锁就会阻塞,不能进入访问临界资源的临界区,直到访问临界资源的线程用完后并解锁。

互斥锁的相关函数主要有如下5个:

#include
//初始化互斥锁
int pthread_mutex_init(pthread_mutex_t* mutex,pthread_mutexattr_t *attr);

//销毁互斥锁,以释放其所占用的内核资源
int pthread_mutex_destroy(pthread_mutex_t* mutex);

//以原子操作给一个互斥锁加锁。如果目标互斥锁已经被锁上,则该函数调用将阻塞,直到该互斥锁的占有者将其解锁
int pthread_mutex_lock(pthread_mutex_t* mutex);

//与pthread_mutex_lock函数类似,不过它始终立即返回,而不论被操作的互斥锁是否已经被加锁
int pthread_mutex_trylock(pthread_mutex_t* mutex);

//以原子操作给一个互斥锁解锁。如果此时有其他线程正在等待这个互斥锁,则这些线程中的某一个将获得它
int pthread_mutex_unlock(pthread_mutex_t* mutex);

 这些函数的第一个参数 mutex 指向要操作的目标互斥锁。 

死锁:在一个线程中对一个已经加锁的普通锁再次加锁,将导致死锁。

死锁使得一个或多个线程被挂起而无法继续执行,而且这种情况还不容易被发现。

3. 条件变量

如果说互斥锁是用于同步线程对共享数据的访问的话,那么条件变量则是用于在线程之间同步共享数据的值。

条件变量提供了一种线程间的通知机制:当某个共享数组达到某个值的时候,唤醒等待这个共享数据的线程。

条件变量是线程的另外一种同步机制,这些同步对象为线程提供了会合的场所,理解起来就是两个(或者多个)线程需要碰头(或者说进行交互,一个线程给另外的一个或者多个线程发送消息),我们指定在条件变量这个地方发生,一个线程用于修改这个变量使其满足其它线程继续往下执行的条件,其它线程则接收条件已经发生改变的信号。

条件变量的相关函数主要有如下5个:

#include
//初始化条件变量
int pthread_cond_init(pthread_cond_t* cond,pthread_condattr_t *cond_attr);

//销毁条件变量
int pthread_cond_destroy(pthread_cond_t* cond);

//以广播的方式唤醒所有等待目标条件变量的线程
int pthread_cond_broadcast(pthread_cond_t* cond);

//唤醒一个等待目标变量的线程
int pthread_cond_signal(pthread_cond_t* cond);

//等待目标条件变量。mutex参数是用于保护条件变量的互斥锁,以确保该函数操作的原子性
int pthread_cond_wait(pthread_cond_t* cond,pthread_mutex_t* mutex);

这些函数的第一个参数 cond 指向要操作的目标条件变量。 

条件变量需要和互斥锁一起使用。

三、线程同步机制包装类 

为了充分复用代码,我们将前面讨论的3种同步机制分别封装成3个类,实现在 lock.h 文件中。

代码清单如下:

#ifndef LOCK_H
#define LOCK_H
#include 
#include 
#include 
#include  
#include 

//信号量,是保护临界资源的一种方法,协调进程对共享资源的访问
//封装信号量的类
class Sem   
{
private:
	sem_t sem;
public:
	Sem();
	~Sem();
	bool wait();
	bool post();
};

Sem::Sem() //创建并初始化信号量
{
	if (sem_init(&sem, 0, 0) != 0)//信号量的初始值和基于内存的信号量 
		std::cerr << "sem init error." << std::endl;
}

Sem::~Sem() //销毁信号量
{
	sem_destroy(&sem);
}

bool Sem::wait()    //等待信号量
{
	return sem_wait(&sem) == 0 ? true : false;
}

bool Sem::post()    //增加信号量
{
	return sem_post(&sem) == 0 ? true : false;
}


//互斥锁:锁机制是同一时刻只允许一个线程执行一个关键部分的代码
//封装互斥锁的类
class Mutex
{
private:
	pthread_mutex_t mutex;

public:
	Mutex();
	~Mutex();
	bool mutex_lock();    //加锁
	bool mutex_unlock(); //解锁
};

Mutex::Mutex()  //创建并初始化互斥锁
{
    //初始化锁,第二个参数指定锁的属性,为“NULL”则使用缺省属性(默认的互斥锁)
    //可用PTHRAD_MUTEX_INITIALIZER宏初始化
	//函数成功完成之后会返回零,其他任何返回值都表示出现了错误。
	if (pthread_mutex_init(&mutex, NULL) != 0)  
		                                                                   
		std::cerr << "mutex init error" << std::endl;
}

Mutex::~Mutex() //销毁锁
{
	pthread_mutex_destroy(&mutex);  //互斥锁销毁函数,在执行成功后返回 0,否则返回错误码
}

bool Mutex::mutex_lock() //加锁
{
	
	return pthread_mutex_lock(&mutex) == 0 ? true : false;
	//当pthread_mutex_lock()返回时,该互斥锁已被锁定。
	//线程调用该函数让互斥锁上锁,如果该互斥锁已被另一个线程锁定和拥有,
    //则调用该线程将阻塞,直到该互斥锁变为可用为止。
}

bool Mutex::mutex_unlock() //解锁
{
	return pthread_mutex_unlock(&mutex) == 0 ? true : false;
	//pthread_mutex_unlock()函数解除锁定 mutex 所指向的互斥锁
}


//条件变量的类
//封装条件变量的类
class Cond
{
private:
	pthread_mutex_t mutex;
	pthread_cond_t cond;

public:
	Cond();
	~Cond();
	bool wait();
	bool signal();
	bool broadcast();
};

Cond::Cond() //创建并初始化条件变量
{
	if (pthread_mutex_init(&mutex, NULL) != 0)   //初始化互斥锁
	{
		std::cerr << "Cond mutex init error" << std::endl;
		exit(0);
	}
	if (pthread_cond_init(&cond, NULL) != 0)   //初始化条件变量
	{
        //构造函数中一旦出现问题,就应该立即释放已经成功分配了的资源
		std::cerr << "Cond cond init error" << std::endl;
		pthread_mutex_destroy(&mutex);  
		exit(0);
	}
}

Cond::~Cond()
{
	pthread_mutex_destroy(&mutex);   //销毁锁
	pthread_cond_destroy(&cond);      //销毁条件变量
}

bool Cond::wait()
{
	int rs = 0;
	pthread_mutex_lock(&mutex);  //调用互斥锁上锁
	rs = pthread_cond_wait(&cond, &mutex);
	pthread_mutex_unlock(&mutex);
	return rs == 0 ? true : false;
}

bool Cond::signal()
{
	//pthread_cond_signal() 激活一个等待该条件的线程
	return pthread_cond_signal(&cond) == 0 ? true : false;
}

bool Cond::broadcast()
{
	//pthread_cond_broadcast() 激活所有等待线程
	return pthread_cond_broadcast(&cond);
}

 

你可能感兴趣的:(Linux,线程同步,信号量,条件变量,互斥锁)