目录
锁的封装
makefile编写
测试运行
RAII式封装
我们今天学习对锁进行封装。
我们在命名空间里面,在自己构建的类mutex里面完成对锁的封装。
锁的封装
我们要进行动态初始化锁,首先要有一个锁对象,所以mutex类里面的私有成员就是锁对象了,然后载初始化里面调用pthread_mutex_init进行初始化,析构函数里面调用pthread_mutex_destroy进行销毁,对锁的操作只需要有解锁和加锁,所以在成员函数lock里面调用pthread_mutex_lock进行加锁,所以在成员函数unlock里面调用pthread_mutex_unlock进行解锁。
#ifndef _THREAD_HPP__
#define _THREAD_HPP__
#include<iostream>
#include<pthread.h>
using namespace std;
//对锁进行封装
namespace lockmodule
{
class mutex
{
public:
mutex()
{
int n = pthread_mutex_init(&_lock, nullptr);
(void)n;
}
void lock()
{
int n = pthread_mutex_lock(&_lock);
(void)n;
}
void unlock()
{
int n = pthread_mutex_unlock(&_lock);
(void)n;
}
~mutex()
{
int n = pthread_mutex_destroy(&_lock);
(void)n;
}
private:
pthread_mutex_t _lock;
};
};
#endif
很简单吧,但是由于之前讲过一块临界资源只能有一个锁的,所有线程都只能调用或者间接指向这个锁,所以为了防止锁被拷贝,所以需要将拷贝构造和拷贝赋值给禁用。
class mutex
{
public:
mutex(const mutex&) = delete;
mutex(mutex&&) = delete; //禁止移动拷贝
const mutex& operator= (const mutex&) = delete;
const mutex& operator= (mutex&&) = delete; //禁止移动拷贝
mutex()
{
int n = pthread_mutex_init(&_lock, nullptr);
(void)n;
}
右值的拷贝构造和赋值可以不用,基本没有人会这么调用。
makefile编写
BIN=ticket
CC=g++
SRC = $(wildcard *.cc)
OBJ = $(SRC:.cc=.o)
$(BIN):$(OBJ)
$(CC) -o $@ $^ -std=c++17 -lpthread
%.o:%.cc
$(CC) -c $<
.PTHONY:clean
clean:
rm -f $(BIN) $(OBJ)
%.o:%.cc这里即使 OBJ 中列出了 .o 文件,make 仍然需要知道如何生成它们, 这行就是在指导make如何生成.o的
OBJ这里:前面的这个范围集合不能有空格。
测试运行
测试代码依旧用的是之间的抢票程序的逻辑,只不过有少许修改。
#include"mutex.hpp"
using namespace lockmodule;
int ticket = 1000;
mutex mtx;
void *route(void *arg)
{
char *id = (char *)arg;
while (1)
{
mtx.lock();
if (ticket > 0)
{
usleep(1000);
printf("%s sells ticket:%d\n", id, ticket);
ticket--;
mtx.unlock();
}
else
{
mtx.unlock();
break;
}
}
return nullptr;
}
int main()
{
pthread_t t1, t2, t3, t4;
pthread_create(&t1, NULL, route, (void*)"thread 1");
pthread_create(&t2, NULL, route, (void*)"thread 2");
pthread_create(&t3, NULL, route, (void*)"thread 3");
pthread_create(&t4, NULL, route, (void*)"thread 4");
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_join(t4, NULL);
}
RAII式封装
我们还可以仿造RAII的形式对锁进行保护,加一个保护类,私有成员自然是这个锁了,然后将一个锁的加锁和解锁分别放在一个这个类的构造和析构函数里面,然后在while循环里面构造保护类的对象,此时由于由于保护类对象属于这个循环区域的,刚刚创建出来的时候会之间构造,调用保护类构造函数加锁之前会先构造这个锁,完成加锁,然后等出了循环自动释放调用析构函数解锁,并调用锁本身的析构函数释放锁资源,从而实现自动加锁+自动释放的RAII形式。
class lockguard
{
public:
lockguard(mutex& mut)
:_mut(mut)
{
mut.lock();
}
~lockguard()
{
_mut.unlock();
}
private:
mutex& _mut;
};
};
#include"mutex.hpp"
using namespace lockmodule;
int ticket = 1000;
mutex mtx;
void *route(void *arg)
{
char *id = (char *)arg;
while (1)
{
lockguard guard(mtx);
if (ticket > 0)
{
usleep(1000);
printf("%s sells ticket:%d\n", id, ticket);
ticket--;
}
else
{
break;
}
}
return nullptr;
}