整理了一下Linux下的同步与互斥的方法,主要有互斥锁,条件变量和信号量。以下分别记录一下:
1. 互斥锁
适用范围 | 线程间的同步与互斥 |
特性 | 包含在pthread线程库中,需要包含<pthread.h>。Linux 下的同一线程无法对同一互斥锁进行递归加速,否则将发生死锁,有两种方法可以解决,第一种是设置互斥量的PTHREAD_MUTEX_RECURSIVE_NP属性,第二种是自己通过引用计数实现,下面的实用代码使用第二种方法 |
相关结构 | pthread_mutex_t mutex; |
相关方法 | /*静态初始化,在声明时就初始化*/ pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
/*动态初始化,第一个参数为互斥对象指针,第二个参数为互斥对象属性,为NULL表示使用默认属性*/ pthread_mutex_init(&mutex, NULL);
/*锁定操作*/ pthread_mutex_lock(&mutex);
/*解锁操作*/ pthread_mutex_unlock(&mutex);
/*销毁互斥量*/ pthread_mutex_destroy(&mutex); |
很好的封装
#include
class Mutex
{
public:
Mutex()
{
m_count = 0;
m_owner = 0;
int ret = pthread_mutex_init(&m_mutex, NULL);
assert(0 == ret);
}
~Mutex()
{
int ret = pthread_mutex_destroy(&m_mutex);
assert(0 == ret);
}
void lock() const
{
pthread_t self = pthread_self();
if (m_owner != self)
{
int ret = pthread_mutex_lock(&m_mutex);
assert(0 == ret);
m_owner = self;
}
m_count++;
}
void unlock() const
{
assert(pthread_self() == m_owner);
if (--m_count == 0)
{
m_owner = 0;
int ret = pthread_mutex_unlock(&m_mutex);
assert(0 == ret);
}
}
pthread_mutex_t *get_mutex()
{
return &m_mutex;
}
private:
mutable pthread_mutex_t m_mutex;
mutable int m_count;
mutable pthread_t m_owner;
};
class MutexLock
{
public:
MutexLock(Mutex& mx):mutex(mx) { mutex.lock(); }
~MutexLock(){ mutex.unlock(); }
private:
Mutex& mutex;
};
#define HT_MX(mx) MutexLock __mutex__(mx)
2. 条件变量
适用范围 | 线程间的同步 |
特性 | 包含在pthread线程库中,需要包含<pthread.h>,需要配合互斥量使用 |
相关结构 | pthread_cond_t cond; |
相关方法 | /*静态初始化,在声明时就初始化*/ pthread_cond_t cond = PTHREAD_COND_INITIALIZER
/*动态初始化,第一个参数为条件对象指针,第二个参数为条件对象属性,为NULL表示使用默认属性*/ pthread_cond_init(&cond, NULL);
/*使线程等待条件变量cond,当cond条件无效时,线程将自动解锁mutex,并进入睡眠状态,直到cond条件有效;当cond条件有效时,若本线程被唤醒,则自动锁定mutex。不难推断出,该函数必须放在pthread_mutex_lock和pthread_mutex_unlock之间*/ pthread_cond_wait(&cond, &mutex);
/*带超时的等待,超时时返回ETIMEOUT*/ pthread_cond_timedwait(&cond, &mutex, ts);
/*使cond条件有效,并唤醒某个阻塞在cond上的线程(根据优先级选择唤醒哪一个线程)*/ pthread_cond_signal(&cond);
/*使cond条件有效,并唤醒所有阻塞在cond上的线程*/ pthread_cond_broadcast(&cond)
/*销毁条件变量*/ pthread_cond_destroy(&cond); |
很好的封装,适用于条件互斥。
class CondMutex
{
public:
CondMutex()
{
int ret = pthread_cond_init(&m_cond, NULL);
assert(0 == ret);
}
~CondMutex()
{
int ret = pthread_cond_destroy(&m_cond);
assert(0 == ret);
}
int wait_with_timeout(Mutex *mutex, int mill) const
{
struct timespec tv;
tv.tv_sec = mill / 1000;
tv.tv_nsec = (mill % 1000) * 1000;
return pthread_cond_timedwait(&m_cond, mutex->get_mutex(), &tv);
}
int wait(Mutex *mutex) const
{
return pthread_cond_wait(&m_cond, mutex->get_mutex());
}
void signal() const
{
pthread_cond_signal(&m_cond);
}
private:
mutable pthread_cond_t m_cond;
};
Mutex mutex;
CondMutex condMutex;
{
HT_MT(mutex);
int ret = condMutex.wait(&mutex);
if (0 == ret)
{ // 成功
......
}
}
3. 信号量
适用范围 | 既可用于线程间的同步,也可用于或进程间的同步(创建命名信号量或将匿名信号量对象放在共享内存中) |
特性 | 信号量是一个整型变量S,它带有两个原子操作wait和signal。wait操作还可以被称为down、P操作。signal操作还可以被称为up、V、post操作。 1、如果S大于0,wait操作就在一个原子操作中对其进行减量运算。如果S等于0,wait操作就就在一个原子操作中测试S,阻塞调用程序,将调用程序放入wait的等待队列中。如果有线程在信号量上阻塞,则S必然等于0。 2、signal操作则会解除对某一个等待线程的阻塞。如果S大于0,即没有线程阻塞在信号量上,signal就对S进行原子增量操作。 3、可以把信号量理解为临界区中资源的可用数量,wait表示对资源的申请,当没有可用资源时信号量为0,signal表示线程使用资源后,对资源的释放。 4、需要要包含头文件<semaphore.h> |
相关结构 | sem_t sem; |
相关方法 | /*初始化匿名信号量,pshared指示该信号量的使用范围是在线程间,还是进程间。当pshared为0时,表示信号量只能在本进程的线程单使用,当pshared为非0时,信号量可以在进程单使用,但信号量必须存在于共享内存中,所有可以访问该共享内存的进程均可以使用该信号量,value表示初始信号量的值,必须>=0*/ sem_init(&sem, pshared, value);
/*signal操作*/ sem_post (&sem);
/*wait操作*/ sem_wait (&sem);
/*销毁信号量*/ sem_destroy (&sem);
/*创建命名信号量,参数oflag用来确定是创建信号量,还是仅仅由函数对其访问。若oflag中的O_CREAT比特位被设置,则需要另外两个参数,mode_t mode为文件权限,unsigned value为信号量值。*/ sem_t* pSem = sem_open(const char* name,int oflag,...);
/*关掉命名信号量,将该进程为命名信号量分配的空间进行释放。注:一个进程终止时,内核还对其上仍然打开着的所有命名信号量自动执行这样的信号量关闭操作。不论该进程是自愿终止的还是非自愿终止的,这种自动关闭都会发生。但应注意的是关闭一个信号量并没有将他从系统中删除。这就是说,Posix命名信号量至少是随内核持续的:即使当前没有进程打开着某个信号灯,他的值仍然保持。*/ sem_close(pSem);
/*从系统中删除信号量。每个信号量有一个引用计数器记录当前的打开次数,sem_unlink必须等待这个计数为0时才能把name所指的信号量从文件系统中删除。也就是要等待最后一个sem_close发生。*/ sem_unlink(const char* name); |