? linux ? ? 服务端 ? ? 多线程 ? ? c++ ?    发布于 2018-12-14   250人围观   0条评论

前言

最近在读陈硕的moduo网络库的书,记录总结一些东西。

本章分析和设计一个高效的多线程日志库。

功能需求

  • 必要的需求
    • 日志级别设定
  • 非必要需求
    • 日志目的地 - 对于诊断日志来说,日志目的地即本机,因为往网络写日志消息并不可靠,诊断日志的功能之一就是诊断网络故障。
    • 日志消息格式可配置 - 可变可不变,尽量保持日志解析的方便程度,即尽量保持日志格式的不可变性
    • 日志运行时过滤器 - 控制不同组件输出不同的日志级别,可要可不要

性能需求

简而言之,就是高效,日志前端写入不会阻塞或尽量不阻塞。对日志消息前端而言,要做到低延迟、低CPU开销、少阻塞;对于日志消息后端而言,要做到足够大的吞吐量以及占用较少资源。

书中列出了几个具体指标:

  • 每秒写几千上万条日志消息时,不会带来明显的性能损失
  • 能应对一个进程产生大量日志数据的场景,例如1GB/min
  • 不阻塞正常执行的流程
  • 在多线程程序中,不造成争用

多线程异步日志

在多线程服务程序中,如果在网络IO或者业务线程中直接往磁盘写数据,写操作可能会比较耗时,直接导致请求方超时。故,在常规的实时业务处理流程中应该避免磁盘IO。

以下过程仅仅是自述理解使用,具体过程建议看书,书中写的非常详细。

muduo网络使用的双缓冲技术,即准备两块buffer,A和B,然后日志前端会兵乓式往A和B写入日志消息,日志后端会处理不在使用的那块buffer中的日志消息。在实施过程中准备了四块buffer,日志前端持有A和B,日志后端持有C和D。日志前端和后端分别持有两块Buffer Vector,V1和V2。

日志前端往A中写数据,写满时将A压入V1,用B替换A,之后继续往B中写入数据,通知日志后端处理数据。

日志后端处理过程为,等待日志前端写满A的通知,压入当前正使用的buffer到V1中,并替换日志前端的A和B为日志后端的C和D。日志后端交换V1和V2,该操作即清空日志前端的Buffer Vector,并将原Vector交于后端处理。日志后端在写入日志数据到磁盘之后,重新用A和B填满后端所需要的两块buffer。

查看更多
? linux ? ? c++ ? ? 多线程 ?    发布于 2018-12-08   571人围观   0条评论

前言

最近在读陈硕的moduo网络库的书,记录总结一些东西。

这章内容比较散,主要介绍多线程系统编程需要理解的一些知识点。看完的整个感受就是可以直接看最后的总结就行了,中间有些内容不是非常能理解的透彻。

基本线程原语

基本线程原语只需要三样东西:thread、mutex、condition。

  1. thread的创建与销毁
  2. mutex的创建、销毁、加锁、解锁
  3. condition_variable的创建、销毁、通知、广播、等待

有了这三样的东西,可以完成任何多线程编程任务。通常,不会直接使用thread和condition_variable,取而代之的是,高级的编程构建,比如线程池,比如之前提到过的CountDownLatch用于主线程和其他线程之间的同步。

C++系统库的线程安全性

这一节中的内容有些东西我还没能够完全理解,现下只记录下一些可理解的内容。以后回过头来再看看。

C++的标准库容器和std::string都不是线程安全的,只有std::allocator保证是线程安全的。一方面的原因是为了避免不必要的性能开销,另一方面的原因是单个成员函数的线程安全并不具备可组合性。

C++标准库中的绝大多数泛型算法是线程安全的,因为这些都是无状态纯函数。只要输入区间是线程安全的,那么泛型算法就是线程安全的。

 

Linux上的线程标识

linux上的线程标识不适合使用pthread_t,原因很多,详情见书。取而代之,使用的是gettid(2)系统调用,其优势书中也写的很清楚。

为了避免效率问题,使用__thread关键字做了缓存,避免每次获取线程id时都需要执行一次系统调用。

线程的创建与销毁的守则

线程的创建

线程的创建需要遵循以下几个原则:

  1. 程序库不应该在未提前告知的情况下创建自己的背景线程
  2. 尽量用相同的方式创建线程
  3. 在进入main()函数之前不应该启动线程
  4. 程序中线程的创建最好能在初始化阶段全部完成,不要为了每个计算任务或者每个网络连接去实时创建线程

以上四点都比较直观,也很容器理解。

针对第一点,如果程序库需要使用背景线程,那么最好让使用者在初始化库时传入线程池或者event loop对象,这样做是为了方便统筹线程的数目和用途,避免低优先级程序的任务独占某个线程。如果程序库在未告知的情况下使用了额外线程,那么会使得我们在规划线程资源的时候漏算一部分,甚至可能使得关键任务的计算资源无法达到性能指标。

针对第二点,统一的方

查看更多
? c++ ? ? linux ? ? 多线程 ?    发布于 2018-12-01   436人围观   0条评论

前言

最近在读陈硕的moduo网络库的书,记录总结一些东西。

读完本章,最大的收获是无论是CPU密集型还是IO密集型,多线程相对于多进程都没有绝对的性能优势。多线程相对于单线程的真正优势是降低平均响应时间。此外,进一步了解最常用的Reactor模型和多线程的一些细节上的东西。

多线程的主要适用场景是计算与IO叠加进行的场景,多进程的主要适用场景是各项任务较为独立的场景。没有银弹,选择多进程还是多线程,需要根据针对不同的场景做不同的设计。

进程与线程

进程与线程之间的区别是老生常谈的知识点,也是我们在设计一个系统时首先要考虑的点。今年阅读和编写了很多多进程和多线程的代码,也看了很多多线程和多进程编程的文章。个人对这两个东西的理解有了很大的提升。下面叙述最近醒悟的几个点:

① 之前我一直以为UI应用就应该使用多线程模型,其实大错特错。

的确,UI应用一般会有一个UI线程用来事件循环界面上的事件,但是其他脏活累活既可以扔给其他线程、也可以扔给其他进程,前者需要考虑线程之间的逻辑关系,而后者考虑的是进程之间的逻辑关系。

② 如果某个多任务应用需要适应单核环境,那么这时候应该使用状态机还是多线程?

单核环境下,状态机的好处是手动调度可以获取更好的性能,但是协调各个任务之间的关系略显蛋疼。相对来说,多线程更加直观,但是线程在单核上的上下文切换以及同步就比较糟蹋性能了。所以说,具体问题具体分析,特殊情况特殊对待,设计系统这种东西,要看场景,要讲经验。

③ 多核环境下呢?

多核环境下,状态机和多线程,当然首选多线程,这样可以更好的发挥多核处理器的性能。那么既然是多核了,直接上多进程来替代多线程岂不更好?问题来了,单核环境下多线程没有状态机高效,多核环境下多线程没有多进程高效,那么多线程的真正优势是什么?

③ 多线程的真正优势

多线程相对于单进程的真正优势是降低响应时间,提高响应速度。但是,多线程与多进程之间的选择往往视情况而定,这里引用书中原话“在其他条件相同的情况下,可以根据工作集合(work set)的大小来取舍,工作集指服务程序响应一次请求所访问的内存大小。如果工作集较大,那么就用多线程,避免CPU cache换入换出,影响性能;否则,就用单线程多进程,享受单线程编程的遍历。

即,多线程和多进程的共享内存同步和通信都比较麻烦,但是如果工作集较大,则多线程避免cpu cached换入换出。但是如果用不了

查看更多
? c++ ? ? linux ? ? 多线程 ?    发布于 2018-11-07   634人围观   0条评论

前言

最近在读陈硕的moduo网络库的书,记录总结一些东西。

在c++多线程编程中,线程间可能会使用跨线程对象,那么对象在什么时候销毁便成了一个让人头疼的问题,因为一个线程在使用这个对象时,另外一个线程可能正在析构这个对象。该章内容就是为我们解读,如何正确的处理跨线程对象的生命周期,以避免内存泄露、空悬指针或者使得程序处于不确定状态等问题。

线程安全

什么是线程安全?这里给出书中的定义:

一个线程安全的class应当满足以下三个条件:

  • 多个线程同时访问时,能够表现出正确的行为
  • 无论操作系统如何调度这些线程,无论这些线程的执行顺序如何交织
  • 调用段代码无须额外的同步或者其他协调动作

依据这个定义,c++标准库中大多数class都不是线程安全的,包括std::string、std::vector、std::map等,因为这些class通常需要在外部加锁才能供多个线程同时访问

个人的理解就是,一个线程安全的类,即使不用在外部加锁,也可以在多个线程中同时访问,想想,实现这样的类,就很难,这也是为什么多线程编程是我一直想去积累一些东西的原因,这是座山,而我必须得翻过。

多线程中的析构与智能指针

由于在多线程环境中,一个对象可以同时被多个线程访问,这导致析构该对象的时机变得模糊不清。很有可能该对象在一个线程中被访问,而同时在另外一个线程中被析构,这些都会造成程序处于未知的状态。那么我们是否可以加锁来保证析构时,其他线程不会访问呢?遗憾的是,析构时锁作为对象的一部分也会被析构,这样就无法对临界区进行保护。

实际上,我们在多线程编程使用原始指针时,不得不思考一个问题,那就是什么时候对象的析构才是安全的?只有其他线程都访问不到该对象的时候才是安全的。自然而然,我们可以想到使用智能指针来引入一层间接性的引用计数。对于跨线程对象而言,最好使用标准库中的智能指针进行处理,智能指针的核心是引用计数。智能指针的思想符合用对象管理资源的思路,实际上,现代c++程序一般不会出现内存泄露的情况,除非特别情况,一般都会使用容器或者智能指针来管理资源。

用智能指针代替原始指针的一大好处是原始指针无法判断所指内存上的对象是否销毁,而只要有智能指针引用计数不为0,所指涉内存上的对象便不会被销毁。

观察者模式与智能指针

 

#include <algorithm>
#include <vector>
#include <stdio.h>

cl
查看更多
? linux ? ? ubuntu ? ? ascii ? ? 图像 ?    发布于 2017-12-09   498人围观   0条评论

图像转ascii码文本

环境: ubuntu 16.04

安装 aview

  1. sudo apt aview

aview的作用是将pnm格式的图片转换为ASCII文本,并显示。aview只支持pnm、pgm、pbm和ppm格式的图片,所以需要将其他格式的图片转换,它提供了一个asciiview的脚本,利用convert转换图片格式,然后再传递给aview。
因此,直接可以执行

  1. asciiview laotouzi.jpg -driver X11

laotouzi.jpg 为我处理的图片,-driver是要选择的设备,这里选择X11,是为了可以改变字体,以提高/减小分辨率

改变字体大小

  1. 为改变字体大小,需要执行
  1. export AAFont=-*-fixed-bold-*-*-*-*-35-*-*-*-*-*-*

设置环境变量,这里设置字体大小35
2. 然后再执行asciiview的命令即可看到改变后的字体大小

其他

jp2a 是另一个工具,用来转换的工具

查看更多