从《计算机网络》到网络编程科班的同学大多学过计算机网络,而非科班的同学也多多少少听说过一些
计算机网络体系十分繁杂且精妙,这三四十年来计算机网络技术不断进步,但是核心是TCP/IP、UDP协议
从计算机到计算机网络我们说一个计算机中的进程可以处理一定的任务
当他想与另一个进程进行通信时,就需要IPC的各种方式
但无论是管道、共享内存都是利用内存作为中转
如果想让另一台计算机的进程也能与这个计算机的进程进行通信呢
聪明的你肯定想到,把内存连起来呗,让他们都能访问到就行
诶,恭喜你,发明了计算机网络
所谓的计算机网络其实就是若干台计算机进程之间进行通信的一个过程
但是这里就有了很多问题
当时的计算机系统各异,Linux、MacOS、Windows,怎么样让他们也能进行通信,甚至同一个操作系统搭载在不同型号的计算机上都有可能不同
如果传输过程中出错了怎么办,要重传吗,还是将就着用
如果计算机之间怎么认得彼此,要知道计算机网络中可不止两台计算机,找到计算机了之后又要怎么找到你想找的进程呢
怎样让我在合肥的计算机访问到在北京的服务器
为什么无线网跟有线网都能进行通信呢
怪不得...
线程池线程池可以说是把之前所有的内容全部串联起来的一个项目
我们这里实现一个简单的版本,可以对其进行扩展
线程池也是一种生产者消费者模型
生产者布置任务而消费者处理任务
主要运用的场景是需要大量现场完成任务,任务完成时间较短,例如WEB服务器中的网页请求
线程池的使用非常简单
就是创建固定数量的线程,然后往任务列表里推送任务即可
线程池会自动分派线程去完成
我们将之前的所有内容可以串联起来做一个小型项目,非常建议阅读并自行实现,写代码才是学习编程的最好方式
互斥锁和信号量封装
线程库封装
日志系统
日志系统完善这里主要完善了日志系统写的方式,可以向屏幕输出、单个文件输出、按照等级划分的文件输出
借用了隔壁日志系统的代码,那边也还没写完
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051#pragma once#include "level.hpp"#include "format.hpp"#includ...
POSIX信号量POSIX信号量和System V信号量是不同的标准
但是实现的功能是一样的,都是为了解决同步的问题
我们说信号量指的就是资源的数量
在生产者与消费者模型里面,生产者与消费者所认为的资源是不同的
生产者认为空间是资源,因为每次都要往里面放东西,空间会变少
消费者认为数据是资源,因为每次都会拿东西,数据会变少
POSIX的操作初始化12#include<semaphore.h>int sem_init(sem_t *sem, int pshared, unsigned int value);
第一个是声明的信号量,第二个是选项,0表示线程间共享,非0表示进程间共享,第三个表示信号量的初始值
销毁1int sem_destory(sem_t *sem);
等待信号量(申请资源)申请资源就是P操作
1int sem_wait(sem_t* sem);
发布信号量(放下资源)发布信号量就是V操作
1int sem_post(sem_t* sem);
环形队列之生产消费模型上一篇文章的生产消费模型是基于阻塞队列的,而且只用了互斥同步锁的内容,我们感觉效率...
生产者消费者模型生产者消费者模型是一个多线程的应用模型
主要分为两个角色,生产者和消费者,这两个角色又可以用多个线程实际运作
这两者之间需要对生产者生产的产品,进行交互,具体来说就是使用阻塞队列来进行产品的交互
这里的产品可以是很多东西,我们可以认为产品是一个类,这个类可以是一切
在整个生产、消费的过程中,有三种关系
生产者与生产者之间的关系,我们不允许两个生产者共同写,他们之间是互斥关系
消费者与消费者的关系,我们也不允许两个消费者共同取,他们之间也是互斥关系
生产者与消费者之间的关系,不允许一边写一边取,这是互斥关系,并且要求先写后取,这是互斥关系
为了更方便的使用互斥锁,我们把系统提供的互斥锁接口进行封装,并且贯彻一下RAII思想,让我们的使用更加方便
互斥锁的封装1234567891011121314151617181920212223242526272829303132333435363738class Mutex // 互斥锁,具体的锁在外部定义,传进来进行调用{public: Mutex(pthread_mutex_t *lock) :...
线程互斥我们之前介绍过互斥的概念,但没有介绍Linux中线程互斥的操作
互斥存在的必要性是因为访问共享资源时,有可能被CPU换下,这样就会产生bug
例如说我们上一篇中说过的抢票,我们虽然设置的是当ticket小于等于0时结束线程
但是当五个线程几乎同时进入这个判断,就会导致过量运行
主要是因为这些操作并非原子的,不是原语
要解决这个问题就需要互斥锁
互斥锁的使用互斥锁的使用一般分为四个步骤
初始化互斥锁
临界区前加锁
临界区后解锁
用完后销毁
初始化互斥锁这里有两个方法,第一个是静态分配
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
第二个是动态分配
int pthread_mutex_init(pthread_mutex_t* restrict mutex, const pthread_mutexattr_t* restrict attr)
第一个是要初始化的互斥量,我们在调用之前声明一共传入即可,第二个我们传入NULL
加锁和解锁int pthread_mutex_lock(pthread_mutex_t* mute...
C++基于多设计模式下的同步和异步日志系统主要功能本项目主要实现一个日志系统,主要支持以下功能
支持多级别的日志消息
支持同步日志和异步日志
支持可靠写入日志到控制台、文件、滚动文件中
支持多线程并发写入日志
支持扩展不同的日志落地目标地
开发环境
WSL(Ubuntu 22.04)
VSCode
g++/gdb
Makefile
核心技术点
类的继承和多态
C++11多线程、智能指针、右值引用
双缓冲区
生产者消费者模型
多线程
设计模式(单例模式、工厂模式、代理模式、建造者模式)
日志系统介绍在生产环境中,有时候是不允许我们程序员利用调试器排查问题,不允许服务暂停
在高频操作中,少量调试次数并不一定能够复现出对应的bug,可能需要重复操作非常多次的情况,导致效率低下
在分布式、多线程代码中,bug更难以定位
因此就需要日志系统进行开发问题的排查
技术实现日志系统的技术实现主要分为两大类
输出日志到控制台
输出日志到文件或数据库系统
这里分为同步和异步写日志
同步日志同步日志是指当输出日志时,程序必须等待日志输出语句执行完毕后才能执行后面的逻辑语句,日...
线程封装我们之前介绍过pthread的线程库,这个线程库主要是基于C语言的void*指针来进行传参和返回
我们使用C++的模板对其封装可以让他的使用更加方便,并且经过测试可以让我们更加直观的了解到线程互斥和同步的重要性
主要框架要对线程库进行封装,但是首先这个线程库的基本功能肯定要有
void*使用模板解决,函数指针使用包装器解决
那么基本框架就能搭建出来了
1234567891011121314151617181920212223242526272829303132333435363738#pragma once#include<iostream>#include<string>#include<functional>#include<pthread.h>template<class T>using Func_t = std::function<void(T)>; // 参数类型为T 返回值为空的函数template<class T>class Thread{public: Th...
设计模式我们以前学的面向过程、面向对象,还有封装、继承、多态
这些就像是武功里面的内功,练到了会用了就是会,而不会想学也都是从0到1,一步步学习练习
而设计模式就像是武功里的外功,属于是学会了,但是要怎么样才能把这个功力的作用最大化,在哪些条件下用什么样的内功能破解,研究的是这个问题
例如,我想设计一个只能在栈或堆上实例化的类应该怎么做,我想设计一个不能被拷贝或继承的类应该怎么做,这些就是外功,是有固定章法的
但是在C++中,设计模式关注的内容没有那么多那么全,我们暂时只了解单例模式即可
特殊类的设计不能被拷贝的类在C++11中,引入了delete关键字,可以直接禁用拷贝构造和赋值重载
在C++98中,虽然没有这个关键字,但是我们可以直接把类的拷贝构造和赋值重载设置为私有即可
12345678910111213141516171819// C++11class A {public: A(int x = 1) :num(x) {};、 A(const A& a) = delete; A& operator=(const A& a...