sylar高性能服务器-日志(P21-P25)内容记录


如果你在这几节视频中遇到了代码编译问题,那么下列内容会有帮助。

P21:线程01

​ 本节内容主要包括搭建一个基础的线程类,按照视频的代码编译没问题,但是运行后可能出现报错,下列内容有解决方法。

一、方法函数

​ 代码中注释部分仅个人理解

thread.h

#ifndef __SYLAR_THREAD_H__
#define __SYLAR_THREAD_H__

#include <thread>
#include <functional>
#include <memory>
#include <pthread.h>

namespace sylar {

class Thread {
public:
    typedef std::shared_ptr<Thread> ptr;
    Thread(std::function<void()> cb, const std::string& name);
    ~Thread();

    // 获取线程ID
    pid_t getId() const { return m_id; }
    // 获取线程名称
    const std::string& getName() const { return m_name; }
    // 等待线程执行完成
    void join();
    // 获取当前的线程指针
    static Thread* GetThis();
    // 获取当前的线程名称
    static const std::string& GetName();
    // 设置线程名称
    static void SetName(const std::string& name);
private:
    Thread(const Thread&) = delete;
    Thread(const Thread&&) = delete;
    Thread& operator=(const Thread&) = delete;
    // 线程执行函数
    static void* run(void* arg);
private:
    pid_t m_id = -1;                     // 线程ID
    pthread_t m_thread = 0;             // 线程结构
    std::function<void()> m_cb;     // 线程执行函数
    std::string m_name;             // 线程名称
};


}


#endif

thread.cc

#include "thread.h"
#include "log.h"
#include "util.h"



namespace sylar {

// 定义线程局部变量
static thread_local Thread* t_thread = nullptr;
// 定义线程局部变量的名称
static thread_local std::string t_thread_name = "UNKNOW";
// 定义系统日志
static sylar::Logger::ptr g_looger = SYLAR_LOG_NAME("system");

// 获取当前的线程指针
Thread* Thread::GetThis() {
    return t_thread;
}
// 获取当前的线程名称
const std::string& Thread::GetName() {
    return t_thread_name;
}
// 设置线程名称
void Thread::SetName(const std::string& name) {
    if(t_thread) {
        t_thread->m_name = name;
    }
    t_thread_name = name;
}
// 构造函数
Thread::Thread(std::function<void()> cb, const std::string& name)
    :m_cb(cb),m_name(name){
    if(name.empty()) {
        m_name = "UNKNOW";
    }
    // 第一个参数为pthread_t类型,第二个参数一般为空,用于定制线程的不同属性,第三个参数为线程启动函数(注意类型需要是void*),第四个参数表示线程启动函数的参数值
    // 没有时可以设置为null,有的话传入参数地址
    // 整个函数正常时返回0
    int rt = pthread_create(&m_thread, nullptr, &Thread::run, this);
    if(rt) {
        SYLAR_LOG_ERROR(g_looger) << "pthread_create thread fail, rt=" << rt << " name=" << name;
        throw std::logic_error("pthread_create error");
    }
}
// 析构函数
Thread::~Thread() {
    if(m_thread){
        pthread_detach(m_thread);
    }
}
// 等待线程执行完成
void Thread::join() {
    if(m_thread) {
        int rt = pthread_join(m_thread,nullptr);
        if(rt) {
             SYLAR_LOG_ERROR(g_looger) << "pthread_join thread fail, rt=" << rt << " m_name=" << m_name;
                throw std::logic_error("pthread_join error");
        }
        m_thread = 0;
    }
}
// 线程执行函数
void* Thread::run(void* arg) {
    Thread* thread = (Thread*)arg;
    t_thread = thread;
    t_thread_name = thread->m_name;
    thread->m_id = sylar::GetTreadId();//获取线程ID
    // 该函数的作用是设置名称的名称,第一个参数:需要设置/获取 名称的线程;第二个参数:要设置/获取 名称的buffer(16个字符长);
    pthread_setname_np(pthread_self(), thread->m_name.substr(0,15).c_str());

    std::function<void()> cb;
    cb.swap(thread->m_cb); // 在不涉及复制或移动可调用对象的情况下,快速地交换两个function的内容

    cb();
    return 0;
}


}

CmakeList

cmake_minimum_required(VERSION 2.8)
project(sylar)

include (cmake/utils.cmake)

set(CMAKE_VERBOSE_MAKEFILE ON) 
set(CMAKE_CXX_FLAGS "$ENV{CXXFLAGS} -rdynamic -O0 -g -std=c++11 -Wall -Wno-deprecated -Werror -Wno-unused-function -Wno-builtin-macro-redefined")

include_directories(.)
include_directories(/root/Web-learning/sylar/include)
link_directories(/root/Web-learning/sylar/lib)

find_library(YAMLCPP yaml-cpp)
message("***", ${YAMLCPP})

set(LIB_SRC
    sylar/log.cc
    sylar/util.cc
    sylar/config.cc
    sylar/thread.cc
    )

add_library(sylar SHARED ${LIB_SRC})
force_redefine_file_macro_for_sources(sylar) #__FILE__ 重定义该宏为相对路径
#add_library(sylar_static STATIC ${LIB_SRC})
#SET_TARGET_PROPERTIES(sylar_static PROPERTIES OUTPUT_NAME "sylar")

set(LIB_LIB
    sylar
    pthread
    ${YAMLCPP}
    )

add_executable(test tests/test.cc)
add_dependencies(test sylar)
force_redefine_file_macro_for_sources(test) #__FILE__ 重定义该宏为相对路径
target_link_libraries(test ${LIB_LIB})


add_executable(test_config tests/test_config.cc)
add_dependencies(test_config sylar)
force_redefine_file_macro_for_sources(test_config) #__FILE__ 重定义该宏为相对路径
# target_link_libraries(test_config sylar -L/root/Web-learning/sylar/lib -lyaml-cpp)
target_link_libraries(test_config ${LIB_LIB})


add_executable(test_thread tests/test_thread.cc)
add_dependencies(test_thread sylar)
force_redefine_file_macro_for_sources(test_thread) #__FILE__ 重定义该宏为相对路径
target_link_libraries(test_thread ${LIB_LIB})

SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)

二、结果展示

​ 按照sylar本节的代码敲下来后可能遇到下列错误

  • image-20240116095822616
  • core dump(我没遇到,不过看大家评论也能通过下列办法解决)

出现上面两个问题就是m_cb没有初始化,所以我们在构造函数定义时可以给它一个初始值

Thread::Thread(std::function<void()> cb, const std::string& name)
    :m_cb(cb),m_name(name)

编译运行后的结果

image-20240116100052401

查看线程

image-20240116100102404

P22: 线程02

​ 本节内容主要实现互斥量和信号量,因为自己对这些也不太了解,之前仅在网络编程看过系列方法,所以下列内容会有各个库的一些常用方法介绍。

一、方法函数

Semaphore

Semaphore通常被叫做信号量,它可以在多线程环境下协调各个线程的执行。它的值只有0和1,和互斥量类似,若资源被锁住为0,资源可用为1。信号量在创建时需要设置一个初始值,表示同时可以有几个任务可以访问该信号量保护的共享资源,初始值为 1 就变成互斥锁 Mutex,即同时只能有一个任务可以访问信号量保护的共享资源。

​ 下面介绍一下sylar在本节中使用到的一些方法函数:

  1. int sem_init(sem_t *sem, int pshared, unsigned int value);

    功能:创建信号量

    参数:第一个参数指向一个信号对象;第二个参数控制信号量的类型,0表示信号量是当前进程的局部信号量,否则信号量就可以在多个进程间共享;第三个参数为信号对象的初始值

    返回:成功返回0,否则为-1

    应用:sylar定义的信号量构造函数使用该方法创建信号量,失败时给出错误提示。

    Semaphore::Semaphore(uint32_t count) {
        if(sem_init(&m_semaphpre, 0, count)) {
            throw std::logic_error("sem_init error");
        }
    }
    
  2. int sem_post(sem_t *sem);

    功能:信号量值加1

    参数:信号量对象

    返回:成功0,失败-1;

    应用:Semaphorenotify方法

    void Semaphore::notify() {
        if(sem_post(&m_semaphpre)) {
            throw std::logic_error("sem_post error");
        }
    }
    
  3. int sem_wait(sem_t *sem);

    功能:信号量的值加-1

    参数:信号量对象

    返回:成功0,失败-1

    应用:Semaphorewait方法

    void Semaphore::wait() {
        if(sem_wait(&m_semaphpre)) {
            throw std::logic_error("sem_wait error");
        }
    }
    

以上内容参考博客:Semaphore详解

ScopedLockImpl

​ 该模块是局部锁,目的就是为了实现RAII机制,即调用构造函数的时候,对类型T的锁调用lock函数(T可以是互斥量,自旋锁,或者原子锁) ,超出作用域就调用析构函数,析构函数中对T类型的锁调用unlock函数。

template<class T>
struct ScopedLockImpl {
public:
    ScopedLockImpl(T& mutex)
        : m_mutex(mutex) {
            m_mutex.lock();
            m_locked = true;
        }
    ~ScopedLockImpl() {
        unlock();
    }

    void lock() {
        if(!m_locked) {
            m_mutex.lock();
            m_locked = true;
        }
    }

    void unlock() {
        if(m_locked) {
            m_mutex.unlock();
            m_locked = false;
        }
    }
private:
    T& m_mutex;
    bool m_locked;
};

**ReadScopedLockImpl **

​ 给读写互斥量加读锁

// 局部读锁模板
template<class T>
struct ReadScopedLockImpl {
public:
    ReadScopedLockImpl(T& mutex)
        : m_mutex(mutex) {
            m_mutex.rdlock();
            m_locked = true;
        }
    ~ReadScopedLockImpl() {
        unlock();
    }

    void lock() {
        if(!m_locked) {
            m_mutex.rdlock();
            m_locked = true;
        }
    }

    void unlock() {
        if(m_locked) {
            m_mutex.unlock();
            m_locked = false;
        }
    }
private:
    T& m_mutex;
    bool m_locked;
};

**WriteScopedLockImpl **

​ 给读写互斥量加写锁

template<class T>
struct WriteScopedLockImpl {
public:
    WriteScopedLockImpl(T& mutex)
        : m_mutex(mutex) {
            m_mutex.wrlock();
            m_locked = true;
        }
    ~WriteScopedLockImpl() {
        unlock();
    }

    void lock() {
        if(!m_locked) {
            m_mutex.wrlock();
            m_locked = true;
        }
    }

    void unlock() {
        if(m_locked) {
            m_mutex.unlock();
            m_locked = false;
        }
    }
private:
    T& m_mutex;
    bool m_locked;
};

**RWMutex **

​ 读写互斥量封装类

  1. pthread_rwlock_init

    功能:初始化读写锁变量

    参数:读写锁对象;读写锁初始化时的属性,默认填NULL

  2. pthread_rwlock_destroy

    功能:释放读写锁;=此函数只是反初始化读写锁变量,并没有释放内存空间,如果读写锁变量是通过malloc等函数申请的,那么需要在free掉读写锁变量之前调用pthread_rwlock_destory函数=

    参数:读写锁对象

  3. pthread_rwlock_rdlock

    功能:在读模式下锁定读写锁

    参数:读写锁对象

  4. pthread_rwlock_wrlock

    功能:在写模式下锁定读写锁

    参数:读写锁对象

  5. pthread_rwlock_unlock

    功能:不管以何种方式锁住读写锁,都可以用这个函数解锁

    参数:读写锁对象

class RWMutex {
public:
    typedef ReadScopedLockImpl<RWMutex> ReadLock;
    typedef WriteScopedLockImpl<RWMutex> WriteLock;
    RWMutex() {
        pthread_rwlock_init(&m_lock, nullptr);
    }
    ~RWMutex() {
        pthread_rwlock_destroy(&m_lock);
    }
    // 读
    void rdlock() {
        pthread_rwlock_rdlock(&m_lock);
    }
    // 写
    void wrlock() {
        pthread_rwlock_wrlock(&m_lock);
    }
    // 解锁
    void unlock() {
        pthread_rwlock_unlock(&m_lock);
    }

private:
    pthread_rwlock_t m_lock;
};

以上内容参考博客:锁详解

二、结果展示

创建一个计时器count和一个读写锁s_mutex

int count = 0;
sylar::RWMutex s_mutex;

首先在不加锁的情况下,循环计数100000次,看count的结果

void fun1() {
    SYLAR_LOG_INFO(g_logger) << "name: " << sylar::Thread::GetName()
                             << " this.name: " << sylar::Thread::GetThis()->getName()
                             << " id: " << sylar::GetTreadId()
                             << " this.id: " << sylar::Thread::GetThis()->getId();
    for(int i = 0; i < 100000; ++ i) {
        ++ count;
    }
}

从结果可以看到每一个都不同

image-20240116153518292

然后给它加锁,首先在线程初始化时,必须调用信号量的wait函数保证离开线程构造函数作用域时所以的线程都已经初始化完毕,然后在线程调用可执行函数之前使用thread->m_semaphore.notify();下面给刚刚测试的代码加一个写锁

void fun1() {
    SYLAR_LOG_INFO(g_logger) << "name: " << sylar::Thread::GetName()
                             << " this.name: " << sylar::Thread::GetThis()->getName()
                             << " id: " << sylar::GetTreadId()
                             << " this.id: " << sylar::Thread::GetThis()->getId();
    for(int i = 0; i < 100000; ++ i) {
        sylar::RWMutex::WriteLock lock(s_mutex);
        ++ count;
    }
}

从结果看,计数器能一直保持在50000

image-20240116153949592

P23:线程与日志整合01

本节主要内容是将日志与互斥量进行整合,也就是在对日志的属性进行操作时,比如在读取日志格式时我们不希望过程中日志格式被改变,那么在读之前就要加上锁。本节的代码修改内容较少,就不全部列举出来,下列内容主要展示一下加锁和不加锁的区别。

一、方法函数

​ 为了进行测试,我们创建了一个空的互斥量

// 互斥量
class Mutex {
public:
    typedef ScopedLockImpl<Mutex> Lock;

    Mutex() {
        pthread_mutex_init(&m_mutex,nullptr);
    }

    ~Mutex() {
        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;
};


// 空的Mutex,测试线程安全
class NullMutex {
public:
    typedef ScopedLockImpl<NullMutex> Lock;
    NullMutex() {}
    ~NullMutex() {}
    void lock() {}
    void unlock() {}
};

二、结果展示

相关配置

日志配置文件内容:

logs:
    - name: root
      level: INFO
      appenders:
          - type: FileLogAppender
            file: /root/Web-learning/sylar/mutex.txt

测试代码:

​ 注意sylar视频中是无线循环打印,为了防止生成文件过大我这里只设置了100次。

void fun1() {
    SYLAR_LOG_INFO(g_logger) << "name: " << sylar::Thread::GetName()
                             << " this.name: " << sylar::Thread::GetThis()->getName()
                             << " id: " << sylar::GetTreadId()
                             << " this.id: " << sylar::Thread::GetThis()->getId();
    for(int i = 0; i < 100000; ++ i) {
        // sylar::RWMutex::WriteLock lock(s_mutex);
        sylar::Mutex::Lock lock(s_mutex);
        ++ count;
    }
}


void fun2() {
    auto i = 100;
    while(-- i) {
        SYLAR_LOG_INFO(g_logger) << "HHHHHHHHHHHHHHHHHHHHHHHHYYYYYYYYYYYYYYYYY";
    }
    
}

void fun3() {
    auto i = 100;
    while(-- i) {
        SYLAR_LOG_INFO(g_logger) << "=========================================";
    }
}


int main(int argc, char** argv) {
    SYLAR_LOG_INFO(g_logger) << "thread test begin";
    YAML::Node root = YAML::LoadFile("/root/Web-learning/sylar/bin/conf/log2.yml");
    sylar::Config::LoadFromYaml(root);
    std::vector<sylar::Thread::ptr> thrs; // 定义线程池
    for(int i = 0; i < 2; ++ i) {
        sylar::Thread::ptr thr(new sylar::Thread(&fun2, "name_" + std::to_string(i * 2)));
        sylar::Thread::ptr thr2(new sylar::Thread(&fun3, "name_" + std::to_string(i * 2 + 1)));
        thrs.push_back(thr);
        thrs.push_back(thr2);
    }
    // sleep(20);
    for(size_t i = 0; i < thrs.size(); ++ i) {
        thrs[i]->join();
    }
    SYLAR_LOG_INFO(g_logger) << "thread test end";
    SYLAR_LOG_INFO(g_logger) << "count = " << count;
    return 0;
}

加互斥量

​ 从生成的mutex.txt看,一共有398条日志文件

image-20240118160907184

不加互斥量

​ 如果我们没有加锁,则会生成823条日志文件,说明有的日志被重复输出

image-20240118160937311

P24:线程与日志整合02

​ 本节主要内容有下列两点:

  1. 使用自旋锁spinLock去优化锁的性能
  2. 解决生成日志文件过程中,如果生成文件被删除,程序依旧占据磁盘且无法再重新打开日志文件。

一、方法函数

spinLock

sylar进行了加锁与不加锁简单了测试,前者写速度约为8Mb/s,后者约为20Mb/s,所以要使用自旋锁进行优化。简单说一下互斥锁和自旋锁的区别(参考

  1. 互斥锁:
    • 互斥锁是一种独占锁,当线程A加锁成功后,此时互斥锁已经被线程A独占了,只要线程A没有释放手中的锁,线程B就会失败,就会释放掉CPU给其他线程,线程B加锁的代码就会被阻塞。互斥锁加锁失败而阻塞是由操作系统内核实现的,当加锁失败后,内核将线程置为睡眠状态,等到锁被释放后,内核会在合适的时机唤醒线程,当这个线程加锁成功后就可以继续执行。
    • 性能开销成本:两次线程上下文切换的成本。当线程加锁失败时,内核将线程的状态从【运行】切换到睡眠状态,然后把CPU切换给其他线程运行;当锁被释放时,之前睡眠状态的线程会变成就绪状态,然后内核就会在合适的时间把CPU切换给该线程运行;
  2. 自旋锁:
    • 自旋锁通过CPU提供的CAS,在用户态完成加锁和解锁操作,不会主动产生线程上下文切换,所以相比互斥锁来说,会快一些开销小一些。
class Spinlock {
public:
    typedef ScopedLockImpl<Spinlock> Lock;
    Spinlock() {
        pthread_spin_init(&m_mutex, 0);
    }
    ~Spinlock() {
        pthread_spin_destroy(&m_mutex);
    }

    void lock() {
        pthread_spin_lock(&m_mutex);
    }
    void unlock() {
        pthread_spin_unlock(&m_mutex);
    }
private:
    pthread_spinlock_t m_mutex;

};

日志输出优化

void FileLogAppender::log(std::shared_ptr<Logger> logger,LogLevel::Level level, LogEvent::ptr event) {
    if(level >= m_level) {
        uint64_t now = time(0);
        if(now != m_lastTime) {
            reopen();
            m_lastTime = now;
        }
        MutexType::Lock lock(m_mutex);
        if(!(m_filestream << m_formatter->format(logger,level,event))) {
            std::cout << "file error!" << std::endl;
        }
    }
}

二、结果展示

优化文件输出前

​ 可以看到在运行测试文件不断往mutex.txt写入日志时,如果人为删除该文件,那么之后对文件的操作都会报错,并且程序依旧在运行。

image-20240123140421526

优化后

​ 保证日志文件在被删除后程序可以程序重新生成,成功解决该问题。但是sylar这里是通过周期性不断使用reopen()去重新打开文件,如果在文件删除时才通知程序重新生成文件性能会好一点。

image-20240123140705033

P25:线程与配置整合

​ 本节内容主要是给配置中一些操作加锁,详细的代码就不在下列展示,没什么需要注意的。

一、结果展示

​ 可以把我们定义的配置类全部读取出来

image-20240123150618527

image-20240123150600263

总结

​ 线程部分就结束了,几节视频看下来对如何搭建一个基本的线程类、创建和使用不同的锁有了初步的了解。对C++中各个线程的库函数仅仅有了了解,如果自己去使用可能也比较困难,后期等整个视频看完后再去找相关资料弥补,然后再会回来看这几节代码肯定会有很大提升。

  • 13
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

madkeyboard

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值