有一定的操作系统基础,互斥,信号量都有一定了解。 了解一下在项目中用到的锁。
1.首先是是初始化和去初始化,这两个函数都是库文件,可以直接调用,他们包含在头文件<pthread.h>中
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
初始化和去初始化我们一般写在构造函数和析构函数里面,如果初始化返回的不是0,说明初始化失败,抛出异常
除了使用 pthread_mutex_init 函数对 mutex 进行初始化,还可以使用特定的宏在声明 mutex 的时候直接赋值进行静态初始化。
ps:重复初始化一个已经初始化过的锁会导致未知行为。
如果尝试销毁一个锁定状态的mutex会导致未知行为。
2.加锁与解锁也是库函数,直接调用
// 普通加锁,重复加锁会阻塞进程
int pthread_mutex_lock (pthread_mutex_t *__mutex);
// 重复加锁不阻塞进程
int pthread_mutex_trylock (pthread_mutex_t *__mutex);
// 带有超时功能加锁
int pthread_mutex_timedlock(pthread_mutex_t *mutex, const struct timespec *abs_timeout);
// 解锁
int pthread_mutex_unlock (pthread_mutex_t *__mutex);
根据函数原型,函数的返回值都是整数,如果返回结果为0表示加锁成功,返回相应的bool值。
3.定义一个线程
pthread_mutex_t m_mutex;
4.创建线程
#include <pthread.h>
pthread_create (thread, attr, start_routine, arg)
thread 指向线程标识符指针。
attr 一个不透明的属性对象,可以被用来设置线程属性。您可以指定线程属性对象,也可以使用默认值 NULL。
start_routine 线程运行函数起始地址,一旦线程被创建就会执行。
arg 运行函数的参数。它必须通过把引用作为指针强制转换为 void 类型进行传递。如果没有传递参数,则使用 NULL。
5.终止线程
#include <pthread.h>
pthread_exit (status)
pthread_exit() 函数是在线程完成工作后无需继续存在时被调用。
如果 main() 是在它所创建的线程之前结束,并通过 pthread_exit() 退出,那么其他线程将继续执行。否则,它们将在 main() 结束时自动被终止。
6.关于上述应用实例
#include <pthread.h>
#include <stdio.h>
int gValue=0;
pthread_mutex_t gMutex = PTHREAD_MUTEX_INITIALIZER;
void *add(void*){
pthread_mutex_lock(&gMutex); // 加锁
for (int i = 0; i < 10; ++i) {
printf("[1]%d ", ++gValue);
}
pthread_mutex_unlock(&gMutex); // 解锁
}
void *sub(void*){
pthread_mutex_lock(&gMutex); // 加锁
for (int i = 0; i < 10; ++i) {
printf("[2]%d ", --gValue);
}
pthread_mutex_unlock(&gMutex); // 解锁
}
int main() {
pthread_t p1, p2;
pthread_create(&p1, NULL, add, NULL);
pthread_create(&p2, NULL, sub, NULL);
pthread_join(p1, NULL);
pthread_join(p2, NULL);
return 0;
}
关于条件变量
1.定义
pthread_cond_t m_cond;
2.初始化和去初始化,同样写在构造函数和析构函数中
返回值:函数成功返回0;任何其他返回值都表示错误
int pthread_cond_init(pthread_cond_t *cv,
const pthread_condattr_t *cattr);
参数是线程和属性:初始化一个条件变量。当参数cattr为空指针时,函数创建的是一个缺省的条件变量。否则条件变量的属性将由cattr中的属性值来决定。这个函数返回时,条件变量被存放在参数cv指向的内存中。
cond()
{
if (pthread_cond_init(&m_cond, NULL) != 0)
{
//pthread_mutex_destroy(&m_mutex);
throw std::exception();
}
}
~cond()
{
pthread_cond_destroy(&m_cond);
}
释放条件变量。
注意:条件变量占用的空间并未被释放。
2.阻塞在条件变量上pthread_cond_wait
函数将解锁m_mutex参数指向的互斥锁,并使当前线程阻塞在m_cond参数指向的条件变量上。
pthread_cond_wait(&m_cond, m_mutex);
pthread_cond_wait函数的返回并不意味着条件的值一定发生了变化,必须重新检查条件的值。
pthread_cond_wait函数返回时,相应的互斥锁将被当前线程锁定,即使是函数出错返回。
一般一个条件表达式都是在一个互斥锁的保护下被检查。当条件表达式未被满足时,线程将仍然阻塞在这个条件变量上。当另一个线程改变了条件的值并向条件变量发出信号时,等待在这个条件变量上的一个线程或所有线程被唤醒,接着都试图再次占有相应的互斥锁。
阻塞在条件变量上的线程被唤醒以后,直到pthread_cond_wait()函数返回之前条件的值都有可能发生变化。所以函数返回以后,在锁定相应的互斥锁之前,必须重新测试条件值。最好的测试方法是循环调用pthread_cond_wait函数,并把满足条件的表达式置为循环的终止条件。如:
pthread_mutex_lock();
while (condition_is_false)
pthread_cond_wait();
pthread_mutex_unlock();
阻塞在同一个条件变量上的不同线程被释放的次序是不一定的。
pthread_cond_wait()函数是退出点,如果在调用这个函数时,已有一个挂起的退出请求,且线程允许退出,这个线程将被终止并开始执行善后处理函数,而这时和条件变量相关的互斥锁仍将处在锁定状态。
3.阻塞直到指定时间pthread_cond_timedwait
#include <pthread.h>
#include <time.h>
bool timewait(pthread_mutex_t *m_mutex, struct timespec t)
{
int ret = 0;
//pthread_mutex_lock(&m_mutex);
ret = pthread_cond_timedwait(&m_cond, m_mutex, &t);
//pthread_mutex_unlock(&m_mutex);
return ret == 0;
}
函数到了一定的时间,即使条件未发生也会解除阻塞。这个时间由参数t指定
pthread_cond_timedwait函数也是退出点。
4.解除在条件变量上的阻塞pthread_cond_signal
bool signal()
{
return pthread_cond_signal(&m_cond) == 0;
}
函数被用来释放被阻塞在指定条件变量上的一个线程。
必须在互斥锁的保护下使用相应的条件变量。否则对条件变量的解锁有可能发生在锁定条件变量之前,从而造成死锁。
唤醒阻塞在条件变量上的所有线程的顺序由调度策略决定,如果线程的调度策略是SCHED_OTHER类型的,系统将根据线程的优先级唤醒线程。
5.释放阻塞的所有线程pthread_cond_broadcast
bool broadcast()
{
return pthread_cond_broadcast(&m_cond) == 0;
}
函数唤醒所有被pthread_cond_wait函数阻塞在某个条件变量上的线程。