TImyWebServer项目详解(1)-线程同步机制封装类
一、基础知识
(1)RAII(Resouces Acquisition Initialization)
资源获取及初始化
在构造函数中申请分配资源,在析构函数中释放资源,因为C++的语言机制保证了,当一个对象创建的时候,自动调用构造函数,当对象超出作用域的时候会自动调用析构函数,所以,在RAII的指导下,我们应该使用类来管理资源,将资源和对象的生命周期绑定
RAII的核心思想是将资源或者状态与对象的生命周期绑定,通过C++的语言机制,实现状态和资源的安全管理,智能指针是RAII的最好例子
(2)信号量
信号量是一种特殊的变量,它只能去自然数值并且只支持两种操作:等待§和信号(V)。假设有信号量SV(资源的数量),对其的P/V操作如下:
P:如果SV的值大于0,则其自减一,表示还有资源可用,进程可正常继续执行;如果为0,则挂起执行,表示资源已经被占用,进程需要阻塞等待。用在共享资源之前
V: 如果有其他进程因为等待SV而挂起,表明有阻塞进程,则唤醒;若没有,则将SV值加一。用在共享资源之后
最简单的信号量是二进制信号量,只有0和1,两个值
if (sem_init(&m_sem, 0, 0) != 0)//sem_init函数用于初始化一个为命名的信号量{throw std::exception();}
sem_init(&m_sem, 0, 0) 初始化信号量 m_sem,指定信号量用于线程间共享,且初始值为 0。
如果 sem_init 返回非 0 值(表示初始化失败),则抛出一个异常 std::exception()。
解释 sem_init 函数
sem_init 函数用于初始化一个未命名的信号量。它的函数原型如下:
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数解释:
sem:指向信号量的指针。在这里,m_sem 是一个 sem_t 类型的变量,用于表示信号量。
pshared:指定信号量是用于进程间共享还是线程间共享。如果 pshared 的值为
0,则表示该信号量是用于线程间共享(同一个进程中的线程共享)。如果 pshared 的值是非 0,则表示该信号量可以在多个进程之间共享。
value:指定信号量的初始值。在这里,初始值为 0。
返回值:
如果初始化成功,sem_init 返回 0。 如果初始化失败,返回一个非 0 的值。
应用场景
信号量通常用于线程间同步。它可以用来控制对共享资源的访问,确保线程安全。初始化信号量的初始值为 0 表示资源不可用,需要其他线程执行
sem_post 函数增加信号量的值,才能让等待的线程继续执行。
注意事项
异常处理:在多线程编程中,初始化信号量失败可能会导致程序的同步机制出现问题,无法正确运行。因此,处理这种情况非常重要,抛出异常是一个常见的错误处理方式。
信号量的类型:选择正确的 pshared 参数值,以确保信号量在适当的范围内共享。在这里使用 0 表示线程间共享。
(3)互斥量
互斥锁,也叫互斥量,可以保护关键代码段,以确保独占式访问。当进入关键代码段,获得互斥锁将其加锁,离开关键代码段,唤醒等待该互斥锁的线程。
locker()
{if (pthread_mutex_init(&m_mutex, NULL) != 0){throw std::exception();}
}
pthread_mutex_init(&m_mutex, NULL) 初始化互斥锁 m_mutex,使用默认属性。
如果 pthread_mutex_init 返回非 0 值(表示初始化失败),则抛出一个异常 std::exception()。
解释 pthread_mutex_init 函数
pthread_mutex_init 函数用于初始化一个互斥锁。它的函数原型如下:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
参数解释:
mutex:指向互斥锁的指针。在这里,m_mutex 是一个 pthread_mutex_t 类型的变量,用于表示互斥锁。
attr:指向互斥锁属性对象的指针。如果传递 NULL,则使用默认属性。
返回值:
如果初始化成功,pthread_mutex_init 返回 0。 如果初始化失败,返回一个错误码。
应用场景
互斥锁用于保护共享资源,防止多个线程同时访问该资源,从而避免竞争条件。互斥锁是线程同步机制中非常重要的一部分。
注意事项
异常处理:在多线程编程中,初始化互斥锁失败可能会导致程序的同步机制出现问题,无法正确运行。因此,处理这种情况非常重要,抛出异常是一个常见的错误处理方式。
锁的销毁:使用完互斥锁后,需要调用 pthread_mutex_destroy 函数来销毁互斥锁,释放相关资源。
完整示例
以下是一个完整的互斥锁使用示例,包括初始化、加锁、解锁和销毁:
#include <iostream>
#include <pthread.h>
#include <exception>class Locker
{
public:Locker(){if (pthread_mutex_init(&m_mutex, NULL) != 0){throw std::exception();}}~Locker(){pthread_mutex_destroy(&m_mutex);}void lock(){pthread_mutex_lock(&m_mutex);}void unlock(){pthread_mutex_unlock(&m_mutex);}private:pthread_mutex_t m_mutex;
};int main()
{try{Locker locker;locker.lock();// 保护的共享资源std::cout << "Critical section" << std::endl;locker.unlock();}catch (const std::exception &e){std::cerr << "Mutex initialization failed" << std::endl;}return 0;
}
这个示例展示了如何在类中使用互斥锁,包括初始化、加锁、解锁和销毁。在 main 函数中,创建一个 Locker 对象,并使用它来保护一个临界区。如果互斥锁初始化失败,则捕获异常并输出错误消息。
(3)条件变量
1)wait()部分
bool wait(pthread_mutex_t *m_mutex){//条件变量的使用机制需要配合锁来使用//内部会有一次加锁和解锁//封装起来会使得更加简洁int ret = 0;//pthread_mutex_lock(&m_mutex);ret = pthread_cond_wait(&m_cond, m_mutex);//pthread_mutex_unlock(&m_mutex);return ret == 0;}
解释
- 条件变量和互斥锁的配合使用
条件变量(pthread_cond_t)用于线程之间的同步,允许一个或多个线程等待某个条件成立,然后继续执行。
使用条件变量时,必须配合互斥锁(pthread_mutex_t),以确保条件的检查和线程的等待操作是原子的,不会被其他线程打断。 - pthread_cond_wait 函数
pthread_cond_wait 函数用于等待条件变量。
调用 pthread_cond_wait 时,线程会阻塞,直到条件变量被其他线程用 pthread_cond_signal 或 pthread_cond_broadcast 唤醒。
在阻塞等待时,pthread_cond_wait 会自动释放传入的互斥锁 m_mutex。
一旦条件变量被唤醒,pthread_cond_wait 会重新获得互斥锁 m_mutex。
###########################################################
pthread_cond_wait 是 POSIX 线程库中的一个函数,用于条件变量的等待操作。它在多线程编程中非常有用,特别是在线程之间需要同步的情况下。
pthread_cond_wait 的函数原型
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
参数解释
cond: 指向条件变量对象的指针。 mutex: 指向互斥锁对象的指针。 功能 pthread_cond_wait
函数会阻塞调用线程,直到条件变量 cond 被其他线程用 pthread_cond_signal 或
pthread_cond_broadcast 唤醒。函数执行期间会解锁传入的互斥锁 mutex,并在被唤醒后重新获得该互斥锁。
工作原理
解锁互斥锁: 当线程调用 pthread_cond_wait 时,它首先会自动解锁与条件变量关联的互斥锁
mutex。这样,其他线程就可以修改条件变量或互斥锁保护的共享资源。 阻塞等待: 然后,调用线程进入阻塞状态,等待条件变量 cond
被其他线程用 pthread_cond_signal 或 pthread_cond_broadcast 唤醒。 重新加锁:
一旦条件变量被唤醒,线程会重新获得互斥锁 mutex,并返回继续执行。
注意事项
在调用 pthread_cond_wait 之前,互斥锁 mutex 必须已经被锁住。 在等待条件变量时,pthread_cond_wait
会自动释放互斥锁,并在被唤醒后重新加锁。 条件变量和互斥锁通常成对使用,以确保线程安全的等待和通知机制。
#################################################################
- 代码中的注释部分
注释掉的 pthread_mutex_lock 和 pthread_mutex_unlock
表明互斥锁的加锁和解锁已经在其他地方进行,或者加锁和解锁不在此函数内完成,而是由调用此函数的上下文处理。 因为
pthread_cond_wait 函数本身会在其内部处理对互斥锁的释放和重新获得,所以在条件变量等待函数内再显式加锁和解锁是多余的。
示例代码
以下是一个完整示例,展示如何使用条件变量和互斥锁来实现线程同步。
#include <iostream>
#include <pthread.h>
#include <queue>class ThreadSafeQueue
{
public:ThreadSafeQueue() : m_cond(PTHREAD_COND_INITIALIZER), m_mutex(PTHREAD_MUTEX_INITIALIZER) {}void enqueue(int data){pthread_mutex_lock(&m_mutex);m_queue.push(data);pthread_cond_signal(&m_cond);pthread_mutex_unlock(&m_mutex);}int dequeue(){pthread_mutex_lock(&m_mutex);while (m_queue.empty()){pthread_cond_wait(&m_cond, &m_mutex);}int data = m_queue.front();m_queue.pop();pthread_mutex_unlock(&m_mutex);return data;}private:std::queue<int> m_queue;pthread_cond_t m_cond;pthread_mutex_t m_mutex;
};void* producer(void* arg)
{ThreadSafeQueue* queue = static_cast<ThreadSafeQueue*>(arg);for (int i = 0; i < 10; ++i){queue->enqueue(i);std::cout << "Produced: " << i << std::endl;}return NULL;
}void* consumer(void* arg)
{ThreadSafeQueue* queue = static_cast<ThreadSafeQueue*>(arg);for (int i = 0; i < 10; ++i){int data = queue->dequeue();std::cout << "Consumed: " << data << std::endl;}return NULL;
}int main()
{pthread_t producer_thread, consumer_thread;ThreadSafeQueue queue;pthread_create(&producer_thread, NULL, producer, &queue);pthread_create(&consumer_thread, NULL, consumer, &queue);pthread_join(producer_thread, NULL);pthread_join(consumer_thread, NULL);return 0;
}
解释
ThreadSafeQueue 类使用条件变量 m_cond 和互斥锁 m_mutex 来实现线程安全的队列。 enqueue
方法在往队列中添加数据时,会发出信号通知等待的线程。 dequeue 方法在从队列中取数据时,如果队列为空,则会等待条件变量。 生产者线程
producer 和消费者线程 consumer 使用 ThreadSafeQueue 来生产和消费数据,实现线程同步。
这样,wait 函数的实现逻辑在条件变量和互斥锁的配合使用中得到了展示。
注意事项
在调用 pthread_cond_wait 之前,互斥锁 mutex 必须已经被锁住。 在等待条件变量时,pthread_cond_wait会自动释放互斥锁,并在被唤醒后重新加锁。 条件变量和互斥锁通常成对使用,以确保线程安全的等待和通知机制。
2)timewait()部分
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;}
这段代码定义了一个函数 timewait,用于在指定的时间段内等待条件变量。如果在指定时间内条件变量未被触发,线程将返回一个超时错误。
pthread_cond_timedwait 函数
pthread_cond_timedwait 是一个用于等待条件变量的函数,与 pthread_cond_wait 不同的是,它允许设置一个绝对时间作为超时时间。
函数原型
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
参数解释
cond: 指向条件变量对象的指针。
mutex: 指向互斥锁对象的指针。
abstime: 指向 timespec结构体的指针,表示绝对超时时间。
功能
该函数在等待条件变量的同时会自动释放与条件变量关联的互斥锁 mutex。如果在指定的超时时间内条件变量被其他线程用pthread_cond_signal 或 pthread_cond_broadcast唤醒,线程会重新获得互斥锁并返回。如果超时时间到了且条件变量仍未被唤醒,函数返回 ETIMEDOUT。
具体解释
加锁操作: pthread_mutex_lock 和 pthread_mutex_unlock 被注释掉,假设调用 timewait
函数前后互斥锁已经被锁住。 等待条件变量: pthread_cond_timedwait 会在指定时间内等待条件变量 m_cond被触发。在此期间,m_mutex 会被解锁以允许其他线程修改条件变量。如果超时或条件变量被唤醒,pthread_cond_timedwait返回,m_mutex 会重新加锁。 检查返回值: 函数返回值 ret 被用于判断条件变量是否在指定时间内被触发。如果返回值为 0,表示条件变量被成功触发;否则表示超时或发生错误。
示例代码
以下是如何使用 timewait 函数的示例:
#include <iostream>
#include <pthread.h>
#include <time.h>pthread_mutex_t m_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t m_cond = PTHREAD_COND_INITIALIZER;bool timewait(pthread_mutex_t *m_mutex, struct timespec t)
{int ret = 0;ret = pthread_cond_timedwait(&m_cond, m_mutex, &t);return ret == 0;
}void* thread_func(void* arg)
{struct timespec ts;clock_gettime(CLOCK_REALTIME, &ts);ts.tv_sec += 5; // 等待5秒pthread_mutex_lock(&m_mutex);bool result = timewait(&m_mutex, ts);if (result){std::cout << "Condition variable was signaled." << std::endl;}else{std::cout << "Condition variable wait timed out." << std::endl;}pthread_mutex_unlock(&m_mutex);return NULL;
}int main()
{pthread_t thread;pthread_create(&thread, NULL, thread_func, NULL);pthread_join(thread, NULL);return 0;
}
说明
在 main 函数中,创建一个线程并等待 5 秒。 如果条件变量在 5 秒内被唤醒,线程会打印 “Condition variable was signaled.”。 如果超时,线程会打印 “Condition variable wait timed out.”。
该示例展示了如何使用 pthread_cond_timedwait在指定时间内等待条件变量,并根据条件变量是否在超时前被唤醒来执行不同的操作。
3)signal()
bool signal(){return pthread_cond_signal(&m_cond) == 0;}
这段代码定义了一个名为 signal 的方法,用于发出条件变量信号。该方法使pthread_cond_signal 函数来唤醒至少一个等待在条件变量 m_cond 上的线程。函数返回一个布尔值,指示操作是否成功。
pthread_cond_signal 函数
pthread_cond_signal 是 POSIX 线程库中的一个函数,用于唤醒至少一个等待在指定条件变量上的线程。
函数原型
int pthread_cond_signal(pthread_cond_t *cond);
参数解释
cond: 指向条件变量对象的指针。
功能
pthread_cond_signal
唤醒一个等待在条件变量上的线程。如果有多个线程在等待条件变量,则由操作系统决定唤醒哪一个。唤醒的线程从 pthread_cond_wait
或 pthread_cond_timedwait 调用中返回,并重新获得用于等待的互斥锁。
具体解释
发送信号: pthread_cond_signal(&m_cond) 调用 pthread_cond_signal
函数,试图唤醒一个等待在条件变量 m_cond 上的线程。 返回值判断: pthread_cond_signal 返回值为 0
表示操作成功,非 0 表示操作失败。 布尔值返回: return pthread_cond_signal(&m_cond) == 0 判断
pthread_cond_signal 的返回值是否为 0,并返回相应的布尔值。成功时返回 true,失败时返回 false。
示例代码
以下是如何使用 signal 方法的示例:
#include <iostream>
#include <pthread.h>pthread_mutex_t m_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t m_cond = PTHREAD_COND_INITIALIZER;bool signal()
{return pthread_cond_signal(&m_cond) == 0;
}void* waiting_thread(void* arg)
{pthread_mutex_lock(&m_mutex);std::cout << "Waiting for signal..." << std::endl;pthread_cond_wait(&m_cond, &m_mutex);std::cout << "Signal received!" << std::endl;pthread_mutex_unlock(&m_mutex);return NULL;
}void* signaling_thread(void* arg)
{pthread_mutex_lock(&m_mutex);std::cout << "Sending signal..." << std::endl;signal();pthread_mutex_unlock(&m_mutex);return NULL;
}int main()
{pthread_t wait_thread, signal_thread;pthread_create(&wait_thread, NULL, waiting_thread, NULL);sleep(1); // 确保等待线程先运行pthread_create(&signal_thread, NULL, signaling_thread, NULL);pthread_join(wait_thread, NULL);pthread_join(signal_thread, NULL);return 0;
}
说明
等待线程:等待条件变量 m_cond 被唤醒。 唤醒线程:发送信号唤醒等待线程。 在 main
函数中,创建两个线程:一个等待条件变量信号,另一个发送信号。 运行程序时,等待线程首先等待信号,然后唤醒线程发送信号,使等待线程恢复运行。
该示例展示了如何使用条件变量和信号机制在不同线程间进行同步操作。