分类: C++

  • Effective STL 读后感

    这本书的全名叫《Effective STL — 50条有效使用STL的经验》,书其实已经有点历史了,Meyers在2001年时给它写的前言,如今已经2022了。

    正如上面所说,因为这本书已经有些年头了,如果你已经是C++11、14、17、20的使用者,可以把第6章函数子、函数子类、函数及其他的内容快速带过看,因为std::functional以及lambda函数已经让C++函数类支持有了显著提升。

    简略写一下对我而言有用的一些经验:

    第12条:切勿对STL容器的线程安全性有不切实际的依赖
    1. 多线程读是安全的
    2. 多线程对不同容器做写入是安全的
    第14条:使用reserve来避免不必要的重新分配

    尽管我认为这应该是C++ Engineer的常识了,不过还是有好多小朋友不知道这个点。

    第23条:考虑用排序的vector代替关联容器

    可以详细看一下。我觉得根本问题是map类的容器对cache不是很友好。连续内存访问总是很香的。

    第25条:熟悉非标准的散列容器

    写书的时候还没有stl::unordered_map。不过即便放2022年,我也觉得可以看看STL之外的散列容器实现,比如robin-hood-hashing。曾经用过比较坑的一个容器是boost::flat_map,千万别认为它是O(1)的实现。

    第31条:了解各种与排序有关的选择

    <algorithm>头文件里有很多宝藏,在选择自己实现之前,最好看看STL有没有帮你写好了轮子。

    第32条:如果确实需要删除元素,则需要在remove这一类算法之后调用erase

    曾经有个说法是std::move does not actually move anything, 对于std::remove也有点类似。

    第44条:容器的成员函数优先于同名的算法

    如题。

    第47条:避免产生“直写型”(write-only)的代码

    工程上非常中肯的一条经验。如果没有特殊原因(如及其苛刻的性能要求),尽量不要写出非常难懂的代码,不然几个月乃至几年之后你来调试自己写的代码时也会骂娘。

    代码被阅读的次数远远大于它被编写的次数

  • C++ SFINAE 检测是否存在某个名称的成员函数

    SFINAE(Substitution failure is not an error) 被用在很多模板的花式操作里,笔者使用的时候多半是为了将运行时的多态替换成编译器的静态多态。

    简单版:检测一个类T是否存在某名字的成员函数

    举个例子,有如下的类定义:

    class SomeWhatClass {
    public:
      void SomeWhatFunction(int x, double y);
    };

    希望能有一个静态的检测,能够实现

    static_assert(HasSomeWhatFunction<SomeWhatClass>::value, "expecting true");

    那么可以这样[1]:

    template <typename T>
     class HasSomeWhatFunction {
       typedef char one;
       struct two { char x[2]; };
       template <typename C>
       static one test(decltype(&C::SomeWhatFunction));
       template <typename C>
       static two test(…);
     public:
       enum { value = sizeof(test(0)) == sizeof(char) };
     };

    基本逻辑是如果被测试的typename T有成员函数,那么调用HasSomeWhatFunction的静态方法test<T>()时,模板替换是按照返回值类型为one(char)来生效的;反之如果不存在SomeWhatFunction的函数,那么返回类型one的模板替换失败,继续查找,匹配到万能重载(catch-all overload) 的static two test(…)里。

    这种做法的可读性在模板的骚操作里算是好的,但是它无法区分SomeWhatFunction的函数签名(参数类型、返回值类型)。

    普通版:检测一个类T是否存在某签名的成员函数

    接上个例子拓展一下:

    class SomeWhatClass {
    public:
      void SomeWhatFunction(int x, double y);
    };
    
    // 希望能检测函数签名
    static_assert(!HasSomeWhatFunction_Double<SomeWhatClass>::value, "not expecting");
    static_assert(HasSomeWhatFunction_Int_Double<SomeWhatClass>::value, "expecting");

    这个就需要更复杂一些的逻辑了:

     template <typename T>
     class HasSomeWhatFunction_Double {
       typedef char one;
       struct two { char x[2]; };
       
       template <typename C, void (C::*)(double)>
       struct Check;
       
       template <typename C>
       static one test(Check<C, &C::SomeWhatFunction> *);
       
       template <typename C>
       static two test(…);
     public:
       enum { value = sizeof(test(0)) == sizeof(char) };
     };
    
     template <typename T>
     class HasSomeWhatFunction_Int_Double {
       typedef char one;
       struct two { char x[2]; };
       
       template <typename C, void (C::*)(int, double)>
       struct Check;
       
       template <typename C>
       static one test(Check<C, &C::SomeWhatFunction> *);
       
       template <typename C>
       static two test(…);
     public:
       enum { value = sizeof(test(0)) == sizeof(char) };
     };

    可以看到这个实现比之前的多定义了一个内部类Check。在初始化enum value值时会调用test,然后会尝试模板替换,进而想要去特化struct Check。如果失败了(不存在对应签名的函数),那么会回滚到static two test里。

    这个实现其实能够覆盖许多场景了,但是还有一个奇怪的需求满足不了:如果要判断一个类的模板成员函数呢?

    地狱版:检测一个类T是否存在某签名的模板成员函数

    小改一下SomeWhatClass

    class SomeWhatClass {
    public:
      template <typename T>
      void SomeTempMemberFunc(int X, T& t);
    };

    如果需要检测void SomeTempMemberFunc<int, T&>是否存在,咋办? 搜了一圈,解释的最清楚的是参考[2]里面描述的,利用std::declval的解法。

    template <typename T>
    class HasTempMemberFunc {
      struct dummy {};
      constexpr static int int_holder {};
      
      template <typename C, typename P>
      static auto test(P& p) 
      -> decltype(std::declval<C>().SomeTempMemberFunc(int_holder, p), std::true_type());
    
      template <typename C, typename P>
      static std::false_type test(...);
    public:
      static const bool value = std::is_same<std::true_type, 
                                             decltype(test<T, dummy>(nullptr))>::value;
    };

    几个知识点:

    • decltype里利用了含有逗号运算符[3]的表达式;
    • declval可以避开构造函数而使用类成员;

    参考

    [1] writing and using a C++ template to check for a function’s existence http://www.cplusplus.com/forum/beginner/70134/

    [2] SFINAE Hell: detecting template methods https://blog.quasardb.net/2015/04/12/sfinae-hell-detecting-template-methods

    [3] SFINAE示例 https://zh.cppreference.com/w/cpp/language/sfinae#.E7.A4.BA.E4.BE.8B

  • [C++] 在linux或windows上使用direct io

    首先

    Direct IO是一种不用内核缓存的IO, 它可以做到直接将用户空间的内存直接写入磁盘或者将磁盘数据直接读到用户空间的缓冲区,这种策略就是不用内核的缓存而使用用户自己设计的缓存. 需要注意的是,使用DirectIO会完全绕过系统的预取(prefetch)以及页缓存机制,如果不是必须,那么我认为还是优先考虑普通的read或者直接mmap吧

    Linux

    几个方面注意一下就可以了

    • 在调用[open]1时,把O_DIRECT加上。比如int fd = open("/path/to/file", O_DIRECT, O_RDONLY);
    • 用于文件读写的buffer,必须和磁盘的块大小对齐(保守起见一般可以设为4KB)。有两种方法能拿到地址对齐的内存块:

      • 直接使用posix_memalign;
      • 直接new一段内存,然后根据返回的内存地址,往后找到第一个满足对齐要求的地址就可以。这种方法会浪费前面一段空间,不过其实posix_memalign在系统操作的时候”浪费”了;
      • 借用mmap申请MAP_ANONYMOUS匿名映射,addr参数填NULL的话mmap出来的地址是页对齐的(至少是4K对齐),所以可以直接拿来用;
    • 如果需要lseek之类的操作,注意seek的文件位置偏移量必须是磁盘块大小的整数倍;
    • 2.4内核下, 每次文件读写的长度必须是块大小的整数倍(e.g. N * 4KB). Linux 2.6.0+无此要求;

    Windows下

    参考这篇文章,可以知道Windows下也可以启用类似的机制,对应的打开文件flag是FILE_FLAG_NO_BUFFERING.
    用法类似,与Linux不同的地方在于:

    • CreateFileA(...)调用的dwFlagsAndAttributes参数里把FILE_FLAG_NO_BUFFERING填上;
    • 用于文件读写的buffer也是需要对齐的

      • 也可以使用类似的方法_aligned_malloc申请对齐的内存,但是必须注意要使用_aligned_free释放内存,否则runtime error;
  • Boost Python的C++对象, Pickle支持及其原理

    默认用boost python包裹的C++对象是不支持pickle的,如果要用pickle.dumps(obj)的话那会提示错误

    Pickling of "xxx" instances is not enabled.

    这边吐槽一下在最新的代码里,给的reference链接其实还是不可用的。真正正确的是https://www.boost.org/doc/libs/1_74_0/libs/python/doc/html/reference/topics/pickle_support.html

    让你的class支持Pickle协议

    若要让你的C++ Class支持Pickle协议,比较“正统”的方法是利用boost提供的boost::python::pickle_suite. 拿代码说话:

    struct world_t {
        world_t(const string& country) { ... }
    };
    
    struct world_pickle_suite : boost::python::pickle_suite
      {
        static
        boost::python::tuple
        getinitargs(const world_t& w)
        {
          // [可选实现] 返回一个boost::python::tuple元组,其值被用来构造
          // 如果一个类的构造函数**不需要参数**的话,可以不用重载这个方法。
          return boost::python::make_tuple(w.country());
        }
    
        static
        boost::python::tuple
        getstate(const world_t& w)
        {
          // [可选实现] 如果对象的构造函数并不能完全恢复对象的状态,
          // 那么要用此函数返回其状态值
        }
    
        static
        void
        setstate(world_t& w, boost::python::tuple state)
        {
          // [可选实现] 如果对象的构造函数并不能完全恢复对象的状态,
          // 那么要用此函数把state里的状态恢复到w
        }
      };
    
    boost::python::class_<world_t>("world", args<const std::string&>())
      // 定义world_t的boost python包装类
      .def_pickle(world_pickle_suite());
      // ...
    

    需要注意的是,如果在suite里定义了getstate/setstate并且这个类的__dict__属性非空,boost是会报错的。一种可能的情况是你包装的类是一个子类,这时候还需要自己实现__getstate_manages_dict__属性。这边不赘述,可参考这里

    原理简释

    总所周知,Python3里若要在自定义的类里实现可Pickle,至少需要:

    • 它的__dict__属性是可pickle的,或;
    • 调用__getstate__()拿到的返回值是可pickle的,随后会调用__setstate__()方法在unpickle的时候恢复状态.
      但在这些函数的背后,再稍微底层一点其实是通过__reduce__()方法实现的(更严格一点,它会先去寻找__reduce_ex__()是否可用)。这个方法简单来说就是返回一个tuple, 这个tuple有从构建对象到恢复状态所需的各种元素.

    In fact, these methods are part of the copy protocol which implements the __reduce__() special method.
    https://docs.python.org/3/library/pickle.html

    所以boost::python其实自己实现了一个对象的__reduce__()方法,在src/object/pickle_support.cpp里(源文件在这)。主要做了几件事

    1. 拿到当前对象的__class__属性(也就是class object);
    2. 检查对象是否有__safe_for_unpickling__属性。如果没有的话,就是本文最早提到的报错了;
    3. 检查对象是否有__getinitargs__()方法,若有则取值。没有的话,unpickle的时候按照无参数构造来处理;
    4. 检查对象是否有__getstate__()方法,若有则取值;
    5. 检查对象是否有__dict__属性,若有则会检查是否有__getstate_manages_dict__属性并获取这两个属性的值。
    6. 返回tuple(),内容依次为1-5里拿到的值。

    可以看到,这个__reduce__()方法是遵循了Python里的object.__reduce__()协定的。当然了,如果某些使用者觉得继承一个suite类都觉得麻烦或者达不到自己的需求,也可以通过其他手段完成,只要记得自己整一个__reduce__()方法,只需满足Python的协定即可.

    class_obj.attr("__reduce__") = boost::python::object(/*YOUR REDUCE FUNCTION*/);

    再深一点~

    如果你跟我一样无聊,那就来看看cpython自己想要__reduce__()的时候是怎么用到__getstate__()的吧.

    cpython/Objects/typeobject.c这边有个函数叫

    static PyObject* _common_reduce(PyObject *self, int proto)

    ,在pickle protocol>2时会调用`static PyObject *
    reduce_newobj(PyObject *obj)`方法,这个方法是Python里大多数默认对象的默认__reduce__逻辑。其中有一句

    state = _PyObject_GetState(obj,
                    !hasargs && !PyList_Check(obj) && !PyDict_Check(obj));

    _PyObject_GetState里大致是从__dict__或者__slots__属性去获取对象的状态。

    这边再往上一些还能看到有调用_PyObject_GetNewArguments()的,里面的逻辑就会去拿__getnewargs_ex__或是__getnewargs__属性的值作为__new__一个新对象时候的传入参数。这两个函数是Pickle协议文档里介绍的四个推荐覆盖的“高阶”函数之一。与之对应,Python并不希望一般人去自己写一个__reduce__()出来.

    看到这里,再回想一下boost::python,pickle_suite里面的getinitargs()getstate()就分别对应到__getnewargs__()__getstate__()

  • 记录一下找了半天的huge page坑——fork越来越慢的原因

    背景

    之前发现Jupyter Notebook下面,如果数据占用多的话,开多进程池会特别的慢。一开始以为是Python的锅,但是把multiprocessing.pool改成直接用os.fork()调用以后,问题依旧。照理来说unix下面使用fork开进程,会启用copy-on-write机制,内存增长并不是特别明显,但是实际在htop下面看内存仍然会在fork之后增长,并且和进程数量是线性相关的。

    原因

    随后想了老半天,想到了可能和页表有关系。查了一下,跑的服务器上huge page确实被禁用了(不知为何…).

    fork的机制简单地说,是在创建新进程的时候把老的进程控制块(Process Control Block)里内存页表拷贝给了新的PCB——这边具体内存的信息是不拷贝的。由于当时Notebook跑的数据处理任务,里面已经用了不少内存(100GB+),所以拷贝的时候如果用默认的4KB内存页,将会有100 * 1024 * 1024 / 4 = 104,857,600个页表! 按典型一个页表项(Page Table Entry)大小4Bytes计算,一个进程开出来光页表会耗400MB内存.

  • Support parallel XZ decompression for unix (7zip LZMA SDK based, C/C++)

    为unix平台增加XZ多线程解压缩支持(基于7zip LZMA SDK, C/C++)

    Note

    This post has nothing to do with the pixz project. I am talking about decompressing the original xz archive using 7-zip’s LZMA SDK under unix environment.

    Background

    Originally the 7zip’s LZMA SDK (version 19.00) only covers parallel xz decompression for Windows systems. This post shows the C code that adds support for lib pthread, i.e. unix systems.
    Compiling with C++ using the C library should also work, I have tested it on my own box.

    Little Details

    Actually the original writer has completed all the necessary abstraction for the multi-threading pipeline. All I have done is adding some macros and pthread equivalents to Windows threading model.

    Usage

    Replace lzma/C/... with below files. The new code should work on both Windows and Unix systems.

    Git Repo

    See https://github.com/idailylife/lzma-xz-parallel

    Source Code

    Threads.h

    /* Threads.h -- multithreading library
    2017-06-18 : Igor Pavlov : Public domain */
    
    #ifndef __7Z_THREADS_H
    #define __7Z_THREADS_H
    
    #ifdef _WIN32
    #include <windows.h>
    #else
    #include <pthread.h>
    #endif
    
    #include "7zTypes.h"
    
    EXTERN_C_BEGIN
    
    WRes HandlePtr_Close(HANDLE *h);
    WRes Handle_WaitObject(HANDLE h);
    
    #ifdef _WIN32
    typedef HANDLE CThread;
    #define Thread_Construct(p) *(p) = NULL
    #define Thread_WasCreated(p) (*(p) != NULL)
    #define Thread_Close(p) HandlePtr_Close(p)
    #define Thread_Wait(p) Handle_WaitObject(*(p))
    #else
    typedef void* LPVOID;
    typedef pthread_t* CThread;
    #define Thread_Construct(p) *(p) = NULL
    #define Thread_WasCreated(p) (*(p) != NULL)
    #define Thread_Close(p) HandleThread_Close(*(p))
    #define Thread_Wait(p) HandleThread_Join(*(p))
    WRes HandleThread_Close(pthread_t* th);
    WRes HandleThread_Join(pthread_t* th);
    
    #endif
    
    
    typedef
    #ifdef UNDER_CE
      DWORD
    #else
      unsigned
    #endif
      THREAD_FUNC_RET_TYPE;
    
    #define THREAD_FUNC_CALL_TYPE MY_STD_CALL
    #define THREAD_FUNC_DECL THREAD_FUNC_RET_TYPE THREAD_FUNC_CALL_TYPE
    typedef THREAD_FUNC_RET_TYPE (THREAD_FUNC_CALL_TYPE * THREAD_FUNC_TYPE)(void *);
    WRes Thread_Create(CThread *p, THREAD_FUNC_TYPE func, LPVOID param);
    
    #ifdef _WIN32
    
    typedef HANDLE CEvent;
    typedef CEvent CAutoResetEvent;
    typedef CEvent CManualResetEvent;
    #define Event_Construct(p) *(p) = NULL
    #define Event_IsCreated(p) (*(p) != NULL)
    #define Event_Close(p) HandlePtr_Close(p)
    #define Event_Wait(p) Handle_WaitObject(*(p))
    WRes Event_Set(CEvent *p);
    WRes Event_Reset(CEvent *p);
    WRes ManualResetEvent_Create(CManualResetEvent *p, int signaled); // not used
    WRes ManualResetEvent_CreateNotSignaled(CManualResetEvent *p); // not used
    WRes AutoResetEvent_Create(CAutoResetEvent *p, int signaled);
    WRes AutoResetEvent_CreateNotSignaled(CAutoResetEvent *p);
    
    #else
    typedef struct {
      bool state;
      pthread_mutex_t mutex;
      pthread_cond_t cond;
    } event_t;
    
    typedef event_t* CEvent;
    typedef CEvent CAutoResetEvent;
    #define Event_Construct(p) *(p) = NULL
    #define Event_IsCreated(p) (*(p) != NULL)
    
    WRes Event_Close(CEvent* p);
    WRes Event_Set(CEvent *p);
    WRes Event_Reset(CEvent *p);
    WRes Event_Wait(CEvent* p);
    WRes AutoResetEvent_CreateNotSignaled(CAutoResetEvent* p);
    
    #endif
    
    // CSemaphore is not used for decoding
    #ifdef _WIN32
    typedef HANDLE CSemaphore;
    #define Semaphore_Construct(p) *(p) = NULL
    #define Semaphore_IsCreated(p) (*(p) != NULL)
    #define Semaphore_Close(p) HandlePtr_Close(p)
    #define Semaphore_Wait(p) Handle_WaitObject(*(p))
    WRes Semaphore_Create(CSemaphore *p, UInt32 initCount, UInt32 maxCount);
    WRes Semaphore_ReleaseN(CSemaphore *p, UInt32 num);
    WRes Semaphore_Release1(CSemaphore *p);
    #endif
    
    #ifdef _WIN32
    
    typedef CRITICAL_SECTION CCriticalSection;
    WRes CriticalSection_Init(CCriticalSection *p);
    #define CriticalSection_Delete(p) DeleteCriticalSection(p)
    #define CriticalSection_Enter(p) EnterCriticalSection(p)
    #define CriticalSection_Leave(p) LeaveCriticalSection(p)
    
    #else
    /// use mutex instead
    typedef pthread_mutex_t* CCriticalSection
    WRes CriticalSection_Init(CCriticalSection *p);
    WRes CriticalSection_Delete(CCriticalSection *p);
    WRes CriticalSection_Enter(CCriticalSection *p);
    WRes CriticalSection_Leave(CCriticalSection *p);
    
    #endif
    EXTERN_C_END
    
    #endif
    

    Threads.c

    /* Threads.c -- multithreading library
    2017-06-26 : Igor Pavlov : Public domain */
    
    #include "Precomp.h"
    
    #ifdef _WIN32
      #ifndef UNDER_CE
      #include <process.h>
      #endif
    #endif
    
    #include "Threads.h"
    
    #ifdef _WIN32
    static WRes GetError()
    {
      DWORD res = GetLastError();
      return res ? (WRes)res : 1;
    }
    
    static WRes HandleToWRes(HANDLE h) { return (h != NULL) ? 0 : GetError(); }
    static WRes BOOLToWRes(BOOL v) { return v ? 0 : GetError(); }
    
    WRes HandlePtr_Close(HANDLE *p)
    {
      if (*p != NULL)
      {
        if (!CloseHandle(*p))
          return GetError();
        *p = NULL;
      }
      return 0;
    }
    
    WRes Handle_WaitObject(HANDLE h) { return (WRes)WaitForSingleObject(h, INFINITE); }
    
    #else
    /// unix specific functions
    
    WRes HandleThread_Close(pthread_t* th) {
      free(th);
      th = NULL;
      return 0;
    }
    
    WRes HandleThread_Join(pthread_t* th) {
      return pthread_join(*th, NULL);
    }
    
    #endif
    
    
    #ifdef _WIN32
    WRes Thread_Create(CThread *p, THREAD_FUNC_TYPE func, LPVOID param)
    {
      /* Windows Me/98/95: threadId parameter may not be NULL in _beginthreadex/CreateThread functions */
      
      #ifdef UNDER_CE
      
      DWORD threadId;
      *p = CreateThread(0, 0, func, param, 0, &threadId);
    
      #else
    
      unsigned threadId;
      *p = (HANDLE)_beginthreadex(NULL, 0, func, param, 0, &threadId);
       
      #endif
    
      /* maybe we must use errno here, but probably GetLastError() is also OK. */
      return HandleToWRes(*p);
    }
    #else
    pthread_attr_t g_th_attrs[64]; //NOTE: maximum of 64 threads
    size_t g_th_index = 0;
    
    WRes Thread_Create(CThread *p, THREAD_FUNC_TYPE func, LPVOID param)
    {
      *p = malloc(sizeof(pthread_t));
      pthread_t* th = *p;
      int ret = pthread_attr_init(&(g_th_attrs[g_th_index]));
      assert(ret==0);
      ret = pthread_create(th, &(g_th_attrs[g_th_index]), func, param);
      g_th_index++;
      return ret;
    }
    #endif
    
    
    #ifdef _WIN32
    static WRes Event_Create(CEvent *p, BOOL manualReset, int signaled)
    {
      *p = CreateEvent(NULL, manualReset, (signaled ? TRUE : FALSE), NULL);
      return HandleToWRes(*p);
    }
    
    WRes Event_Set(CEvent *p) { return BOOLToWRes(SetEvent(*p)); }
    WRes Event_Reset(CEvent *p) { return BOOLToWRes(ResetEvent(*p)); }
    
    WRes ManualResetEvent_Create(CManualResetEvent *p, int signaled) { return Event_Create(p, TRUE, signaled); }
    WRes AutoResetEvent_Create(CAutoResetEvent *p, int signaled) { return Event_Create(p, FALSE, signaled); }
    WRes ManualResetEvent_CreateNotSignaled(CManualResetEvent *p) { return ManualResetEvent_Create(p, 0); }
    WRes AutoResetEvent_CreateNotSignaled(CAutoResetEvent *p) { return AutoResetEvent_Create(p, 0); }
    
    #else 
    ///unix
    
    WRes Event_Close(CEvent* p) {
      if (!p || !(*p))
        return 0;
      event_t* evt = *p;
      pthread_cond_destroy(&evt->cond);
      pthread_mutex_destroy(&evt->mutex);
      free(evt);
      *p = NULL;
    }
    
    
    WRes Event_Set(CEvent *p) {
      event_t* evt = *p;
      if (pthread_mutex_lock(&evt->mutex) != 0) {
        return 1;
      }
      evt->state = true;
    
      if (evt->manual_reset) {
        if (pthread_cond_broadcast(&evt->cond)) {
          return 1;
        }
      } else {
        if (pthread_cond_signal(&evn->cond)) {
          return 1;
        }
      }
    
      if (pthread_mutex_unlock(&evt->mutex)) {
        return 1;
      }
    
      return 0;
    }
    
    WRes Event_Reset(CEvent* p) {
      event_t* evt = *p;
      if (pthread_mutex_lock(&evt->mutex)) {
        return 1;
      }
    
      evt->state = false;
    
      if (pthread_mutex_unlock(&evt->mutex)) {
        return 1;
      }
    
      return 0;
    }
    
    WRes Event_Wait(CEvent* p) {
      event_t* evt = *p;
      if (pthread_mutex_lock(&evt->mutex)) {
        return 1;
      }
    
      while (!evt->state) {
        if (pthread_cond_wait(&evt->cond, &evt->mutex)) {
          pthread_mutex_unlock(&evt->mutex);
          return 1;
        }
      }
    
      evt->state = false;
      if (pthread_mutex_unlock(&evt->mutex)) {
        return 1;
      }
      return 0;
    }
    
    WRes AutoResetEvent_CreateNotSignaled(CAutoResetEvent *p) {
      *p = malloc(sizeof(event_t));
      memset(evt, 0, sizeof(event_t));
      evt->state = false;
      evt->manual_reset = false;
      if (pthread_mutex_init(&evt->mutex, NULL)) {
        return 1;
      }
      if (pthread_cond_init(&evt->cond, NULL)) {
        return 1;
      }
      return 0;
    }
    
    #endif
    
    
    #ifdef _WIN32
    
    WRes Semaphore_Create(CSemaphore *p, UInt32 initCount, UInt32 maxCount)
    {
      *p = CreateSemaphore(NULL, (LONG)initCount, (LONG)maxCount, NULL);
      return HandleToWRes(*p);
    }
    
    static WRes Semaphore_Release(CSemaphore *p, LONG releaseCount, LONG *previousCount)
      { return BOOLToWRes(ReleaseSemaphore(*p, releaseCount, previousCount)); }
    WRes Semaphore_ReleaseN(CSemaphore *p, UInt32 num)
      { return Semaphore_Release(p, (LONG)num, NULL); }
    WRes Semaphore_Release1(CSemaphore *p) { return Semaphore_ReleaseN(p, 1); }
    
    #endif
    
    
    #ifdef _WIN32
    WRes CriticalSection_Init(CCriticalSection *p)
    {
      /* InitializeCriticalSection can raise only STATUS_NO_MEMORY exception */
      #ifdef _MSC_VER
      __try
      #endif
      {
        InitializeCriticalSection(p);
        /* InitializeCriticalSectionAndSpinCount(p, 0); */
      }
      #ifdef _MSC_VER
      __except (EXCEPTION_EXECUTE_HANDLER) { return 1; }
      #endif
      return 0;
    }
    
    #else
    WRes CriticalSection_Init(CCriticalSection *p) {
      *p = malloc(sizeof(pthread_mutex_t));
      if (pthread_mutex_init(*p, NULL)) {
        return 1;
      }
      return 0;
    }
    
    WRes CriticalSection_Delete(CCriticalSection *p) {
      pthread_mutex_t* mtx = *p;
      return pthread_mutex_destroy(mtx);
    }
    
    WRes CriticalSection_Enter(CCriticalSection *p) {
      if (pthread_mutex_lock(&evt->mutex)) {
        return 1;
      }
      return 0;
    }
    
    WRes CriticalSection_Leave(CCriticalSection *p) {
      if (pthread_mutex_unlock(&evt->mutex)) {
        return 1;
      }
      return 0;
    }
    
    #endif
    

    XzDec.c

    add the following macro

    #ifndef _WIN32
    #define S_OK 0x00000000
    #define E_FAIL 0x80004005
    #endif

    MtDec.c

    replace function ThreadFUnc1 with:

    static THREAD_FUNC_RET_TYPE THREAD_FUNC_CALL_TYPE ThreadFunc1(void* pp)
    {
      static int g_ok_stat = 0x0;
      static int g_err_stat = 0x80004005;
      WRes res;
      CMtDecThread *t = (CMtDecThread*)pp;
      CMtDec *p;
    
      res = ThreadFunc2(t);
      p = t->mtDec;
      if (res == 0) {
    #ifdef _WIN32
        return p->exitThreadWRes;
    #else
        if (p->exitThreadWRes) { return &g_err_stat; }
        else { return &g_ok_stat; }
    #endif
      }
      {
        // it's unexpected situation for some threading function error
        if (p->exitThreadWRes == 0)
          p->exitThreadWRes = res;
        PRF(printf("\nthread exit error = %d\n", res));
        p->exitThread = True;
        Event_Set(&p->threads[0].canRead);
        Event_Set(&p->threads[0].canWrite);
        MtProgress_SetError(&p->mtProgress, MY_SRes_HRESULT_FROM_WRes(res));
      }
    #ifdef _WIN32
        return res;
    #else
        return &g_err_stat;
    #endif
    }
  • std::future 与 std::promise 简单使用、简单原理

    C++11有了一些关于线程的模型,在此之前C++里可是各自为政的,各种线程库各种神奇用法。其中有两个好玩的东西就是std::promise<T>std::future<T>,下文书中形容它们像是个“虫洞”。

    • std::future是虫洞的出口:一个未来对象的“获取器”,在未来的某一刻它能返回一个有用的值,但现在还没有…
    • std::promise是虫洞的入口:我们要保证在未来的某一刻给一个值进去,但不是现在…

    Basic usage

    一般来说这俩是成对使用的,看个代码:

    #include <cassert>
    #include <chrono>
    #include <future>
    #include <iostream>
    #include <string>
    #include <thread>
    
    using namespace std::chrono_literals;
    
    int main()
    {
      std::promise<int> p1, p2;
      std::future<int> f1 = p1.get_future();
      std::future<int> f2 = p2.get_future();
      
      p1.set_value(42);
      assert(f1.get() == 42);
      
      std::thread t([&]() {
        std::this_thread::sleep_for(100ms);
        p2.set_value(43);
      });
      
      auto start_time = std::chrono::system_clock::now();
      assert(f2.get() == 43);
      std::chrono::duration<double, std::milli> elapsed = std::chrono::system_clock::now() - start_time;
      std::cout << "Waited " << elapsed.count() << " ms\n";
      t.join();
      return 0;
    }
    
    // output: Waited 100.253 ms                                                                                                                                                       
    

    一个future对象可以通过promise.get_future()方法创建出来。当我们有真正的值来填进promose对象的时候,就用promise.set_value(v)方法。同时,(一般是在另一个线程里)当准备要获取值的时候,就调用future.get()方法。get方法会保持阻塞状态直到一个有效值被填进去。

    值得一提的是,有一个特化的T = void。这货有啥用呢?可以用来阻塞等待某个并行任务完成:

    std::promise<void> ready_p;
    std::future<void> read_f = ready_p.get_future();
    
    std::thread thread_b([&]() {
        prep_work();
        ready_p.set_value(); // no arg
        main_work();
    });
    ready_f.wait();
    // now that thread B has completed
    

    A bit of details

    需要留意的是,promise也好future也罢,都是有动态内存分配(dynamic memory allocation)的开销的。
    std_promise_future_schema.PNG(图源为参考文献)

    留意图中的那个State对象,它基本上是一个shared_ptr——因为虫洞的两端(很可能是不同线程)都要用到这个共享对象(shared ownership)。所以创建std::promose/std::future的时候都是要申请新的堆空间。

    Reference

    本文全文参考自本书:
    <Mastering the C++17 STL: Make Full Use of the Standard Library Components in C++17>

  • C++17 std::variant 简单使用

    std::variant应当是用来替代union来使用的,后者从C时代就有了,但是它缺乏类型安全的保障。boost库很早之前就有了实现(其实从C++11开始,感觉boost不知道多少东西被借鉴走了)。
    std::variant的类长这个样子:

    template <class... Types>
    class variant;
    

    Usage

    使用起来也是比较方便的

    std::variant<int, double> v1;
    v1 = 1; // activate the "int" member
    assert(v1.index() == 0);
    assert(std::get<0>(v1) == 1);
    
    v1 = 3.14; // activate the "double" member
    assert(v1.index() == 1);
    assert(std::get<1>(v1) == 3.14);
    assert(std::get<double>(v1) == 3.14);
    
    assert(std::holds_alternative<int>(v1) == false);
    assert(std::holds_alternative<double>(v1) == true);
    
    assert(std::get_if<int>(&v1) == nullptr);
    assert(*std::get_if<double>(&v1) == 3.14);
    

    上面代码里的几个好用的constexpr函数:

    • std::get<>:可以传入index或是type。如果index或者类型不对的话,会跑出std::bad_variant_access异常;
    • std::get_if<>:这个是不抛异常的版本。注意参数和返回值类型都是指针——如果没有有效元素,那就会返回nullptr;
    • std::holds_alternative<Type>:判断一个Type在不在定义的variant参数类里面;

    Visiting variants

    如果要根据当前存储的类型来对variant对象做一些操作,第一会想到写一些if语句

    if (std::holds_alternative<int>(v1) == true) {
      // handle int value
    }
    else {
      // handle double value
    }
    

    但是如果variant的可选类型比较多的话,定义一个Visitor类会有助于按类型来执行操作,逻辑会显得清晰

    struct Visitor {
      double operator() (double d) { return d; }
      double operator() (int i) { return double(i); }
    };
    
    void show(std::variant<double, int> v) {
      std::cout >> std::visit(Visitor{}, v) << std::endl;
    }
    
    void test() {
      show(3.14);
      show(1);
    }
    
  • C++17 std::generate的使用

    std::generate是“遍历——执行”工具函数的一种

    #include <algorithm>
    #include <iostream>
    #include <string>
    #include <vector>
    
    int main()
    {
      std::vector<std::string> v(4);
      std::generate(v.begin(), v.end(), [i=0]() mutable {
          return ++i % 2 ? "hello" : "world";
      });
      
      for (auto&& s : v) {
        std::cout << s << " ";
      }
      return 0;
    }
    

    上面的函数输出是hello world hello world .

    std::generate(beginIt, endIt, generator)函数接受起止的迭代器以及一个生成方法。

  • C++17 新特性—— if constexpr

    看代码说话

    template <typename Iterator>
    auto distance(Iterator begin, Iterator end) {
      using Traits = std::iterator_traits<Iterator>;
      if constexpr (std::is_base_of_v<std::random_access_iterator_tag, typename Traits::iterator_category>) {
        return end - begin;
      } else {
        auto result = typename Traits::difference_type();
        for (auto it = begin; it != end; ++it) {
          ++result;
        }
        return result;
      }
    }

    上面的代码用来判断两个iterator之间的距离。主要逻辑是判断iterator是否是可随机访问的iterator然后作分支处理,可以随机访问的迭代器直接把iterator相减。
    constexpr if语句中,条件的值必须是可按语境转换到 bool 类型的经转换常量表达式。若其值为 true,则舍弃 false分支语句(若存在),否则舍弃 true分支语句。有意思的一点是,被舍弃语句中的 return 语句不参与函数返回类型推导,也就是说两个分支语句中的return推倒出来的类型允许不一样。

    如果没有C++17这种怎么写呢?就是比较麻烦。
    一可以加一个参数,但是每个tag写一个

    template <typename Iterator>
    auto distance(Iterator begin, Iterator end, std::bidirectional_iterator_tag); // one for each tag type

    或者用SFINAE写两个

    template <typename Iterator>
    auto distance(Iterator begin, Iterator end, 
                  typename std::enable_if_t<std::is_base_of_v<一大坨>>* = nullptr);