多线程之间同步

时间:2022-09-23 18:29:41

1、POSIX信号量

#include<semaphore.h>
int sem_init(sem_t* sem, int pshared, unsigned int value);
int sem_destroy(sem_t* sem);
int sem_wait(sem_t* sem);
sem_trywait(sem_t* sem);
int sem_post(sem_t* sem);

    这组函数的第一个参数sem标识被操作的信号量。下面分别介绍每个函数:

  • sem_init函数用于初始化一个未命名信号量。pshared参数指定信号量类型,如果为0,则表示该信号量只在当前进程*享,即局部信号量,否则该信号量可以在多个进程*享;value参数指定信号量初始值。如果一个信号量已经被初始化,则再次初始化则会导致不可预期的结果。
  • sem_destroy函数用于销毁一个信号量。如果销毁一个正被其他线程等待的信号量,将导致不可预期的结果。
  • sem_wait函数以原子操作方式将信号量值减1,如果信号量为0,则sem_wait函数将被阻塞,直到这个信号量为非0。
  • sem_trywait函数与sem_wait函数功能类似,但它为非阻塞的,当信号量值为0时,它将返回-1,并设置errno为EAGAIN。
  • sem_post函数以原子操作方式将信号量值加1,如果信号量的值大于0,则其它等待该信号量的线程将被唤醒。

2.1、互斥锁

    互斥锁用户保护临界区代码,保证任一时刻只有一个线程对临界区进行独占式访问,这与二进制信号量功能类似。互斥锁相关函数如下:

#include<pthread.h>
int pthread_mutex_init(pthread_mutex_t* mutex, const pthread_mutexattr_t* mutexattr);
int pthread_mutex_destroy(pthread_mutex_t* mutex);
int pthread_mutex_lock(pthread_mutex_t* mutex);
int pthread_mutex_trylock(pthread_mutex_t* mutex);
int pthread_mutex_unlock(pthread_mutex_t* mutex);

  • 第一个参数mutex标识要操作的互斥锁。
  • pthread_mutex_init函数用于初始化互斥锁,mutexattr参数指定互斥锁的属性,如果设置为NULL,则表示使用默认属性。初始化互斥锁还可以使用另一种方式:
pthread_mutex = PTHREAD_MUTEX_INITIALIZER  //将互斥锁的各个字段初始化为0
  • pthread_mutex_destroy函数用于销毁互斥锁,销毁一个已经加锁的互斥锁将导致不可预期的结果。
  • pthread_mutex_lock函数以原子操作方式给一个互斥锁(普通锁)加锁,如果该互斥锁已经加锁,则函数调用将阻塞,直到该互斥锁被解锁。
  • pthread_mutex_trylock与pthread_mutex_lock功能类似,但是是非阻塞的。当目标互斥锁已经被加锁,则返回错误码EBUSY。
  • pthread_mutex_unlock函数以原子操作方式给一个互斥锁解锁。

2.2、互斥锁属性

    pthread_mutexattr_t结构定义了互斥锁的属性,并且线程库提供一系列的函数来操作该结构。这里不罗列这些函数,只介绍互斥锁较为重要的属性。

  1. pshared指定是否允许跨进程共享互斥锁,可选值有二:

  • PTHREAD_PROCESS_SHARED,互斥锁可以被跨进程共享
  • PTHREAD_PROCESS_PRIVATE,互斥锁只能在一个进程*享   

    2. type指定互斥锁的类型,主要的几个类型如下:

  • PTHREAD_MUTEX_NORMAL,普通锁,默认情况也为普通锁。如果一个线程对一个普通锁加锁,则其余的请求该锁的线程将形成一个队列,当拥有该锁的线程解锁后,队列里的线程将按照优先级来获得该锁。这种锁保证了资源分配的公平性,但是也容易引起问题:一个线程如果对一个自己已经加锁的普通锁再次加锁,则引发死锁;对一个已经被其他线程加锁的普通锁解锁,或者对一个已经解锁的普通锁再次解锁,将导致不可预期结果。
  • PTHREAD_MUTEX_ERRORCHECK,检错锁。一个线程如果对一个自己已经加锁的检错锁进行加锁,则返回EDEADLK。对一个已经被其他线程加锁的检错锁解锁,或者对一个已经解锁的检错锁再次解锁,则操作将返回EPERM。
  • PTHREAD_MUTEX_RECURSIVE,嵌套锁。这种锁允许一个线程在解锁之前多次对它加锁而不发生死锁,不过其他线程如果想要获得该嵌套锁,则该嵌套锁的拥有者必须执行相应次数的解锁操作。对一个已经被其他线程加锁的嵌套锁解锁,或者对一个已经解锁的嵌套锁再次加锁,则会返回EPERM。

3、条件变量

    条件变量用于线程之间同步共享数据的值,即当某个共享数据达到某个值的时候,唤醒等待这个共享数据的线程。

#include<pthread.h>
int pthread_cond_init(pthread_cond_t* cond, const 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);
int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);

  • 函数的第一个参数cond标识要操作的目标条件变量。
  • pthread_cond_init函数用于初始化条件变量,cond_attr为条件变量的属性,如果为NULL,则为默认属性。初始化条件变量还可以使用类似互斥锁的另一种方式,使用宏PTHREAD_COND_INITIALIZER,将条件变量的各个字段初始化为0。
  • pthread_cond_destroy函数用于销毁条件变量,如果销毁一个正在被等待的条件变量将失败并返回EBUSY。
  • pthread_cond_broadcast函数以广播的形式唤醒所有等待该条件变量的线程。
  • pthread_cond_signal函数用于唤醒一个等待目标条件变量的线程,具体唤醒哪个线程,取决于线程的优先级和调度策略。如果想唤醒指定的线程,那么可以采用定义全局变量来标识目标线程,然后使用广播的形式唤醒所有线程,线程被唤醒检查全局变量,判断是否自己被唤醒,如果不是则继续等待。
  • pthread_cond_wait函数用于等待条件变量,mutex参数是用于保护条件变量的互斥锁,以确保pthread_cond_wait操作的原子性。函数调用前,先对条件变量加锁,函数调用将现程放入等待条件变量的队列中,然后将互斥锁解锁,这样做就保证了pthread_cond_wait开始执行到线程被放入等待队列的这段时间内,条件变量不会被修改。当pthread_cond_wait函数成功返回时,互斥锁mutex将再次被锁上。

4、线程同步机制包装类

ifndef LOCKER_H
#define LOCKER_H

#include<exception>
#include<pthread.h>
#include<semaphore.h>

/*封装信号量*/
class sem
{
	public:
		/*创建并初始化信号量*/
		sem()
		{
			if(sem_init(&m_sem, 0, 0) != 0)
			{
				/*构造函数没有返回值,可以通过抛出异常来报告错误*/
				throw std::exception();
			}
		}
		/*销毁信号量*/
		~sem()
		{
			sem_destroy(&m_sem);
		}

		/*等待信号量*/
		bool wait()
		{
			return sem_wait(&m_sem) == 0;
		}

		/*增加信号量*/
		bool post()
		{
			return sem_post(&m_sem) == 0;
		}

	private:
		sem_t m_sem;
};


/*封装互斥锁*/
class locker
{
	public:
		/*创建并初始化互斥锁*/
		locker()
		{
			if(pthread_mutex_init(&m_mutex, NULL) != 0)
			{
				throw std::exception();
			}
		}
		/*销毁互斥锁*/
		~locker()
		{
			pthread_mutex_destroy(&m_mutex);
		}
		/*获取互斥锁*/
		bool lock()
		{
			return pthread_mutex_lock(&m_mutex) == 0;
		}
		/*释放互斥锁*/
		bool unlock()
		{
			return pthread_mutex_unlock(&m_mutex) == 0;
		}

	private:
		pthread_mutex_t m_mutex;
};

/*封装条件变量类*/
class cond
{
	public:
		/*创建并初始化条件变量*/
		cond()
		{
			if(pthread_mutex_init(&m_mutex, NULL) != 0)
			{
				throw std::exception();
			}
			if(pthread_cond_init(&m_cond, NULL) != 0)
			{
				pthread_mutex_destroy(&m_mutex);
				throw std::exception();
			}
		}
		/*销毁条件变量*/
		~cond()
		{
			pthread_mutex_destroy(&m_mutex);
			pthread_cond_destroy(&m_cond);
		}
		/*等待条件变量*/
		bool wait()
		{
			int ret = 0;
			pthread_mutex_lock(&m_mutex);
			ret = pthread_cond_wait(&m_cond, &m_mutex);
			pthread_mutex_unlock(&m_mutex);
			return ret == 0;
		}
		/*唤醒等待条件变量的线程*/
		bool signal()
		{
			return pthread_cond_signal(&m_cond) == 0;
		}

	private:
		pthread_mutex_t m_mutex;
		pthread_cond_t m_cond;
};

#endif