Python模块包含的类创建(下)

  1. 类的方法表创建
  • 直接上代码
static PyMethodDef VCam_MethodMembers[] =      //类的所有成员函数结构列表同样是以全NULL结构结束
  {
      { "set_fill", (PyCFunction)VCam_SetFill, METH_VARARGS, "Set video resize method (0: Aspect fit, 1: Aspect fill, 2: Stretch), used when input frame size differs from VCam output size." },
      { "mirror", (PyCFunction)VCam_Mirror, METH_VARARGS, "Mirror the output video (0: no mirror, others: mirror), non-persistent." },
      { "rotate", (PyCFunction)VCam_Rotate, METH_VARARGS, "Rotate the input video 90 degree (0: no rotate, others: rotate), non-persistent." },
      { "flip", (PyCFunction)VCam_Flip, METH_VARARGS, "Vertical flip the output video(0: no flip, others : flip), non - persistent." },  
      { "send_image", (PyCFunction)VCam_SendImg, METH_VARARGS, "Display a image( path) to vCam." },
      { "capture_screen", (PyCFunction)VCam_CaptureScreen, METH_VARARGS, "Capture region of screen and set it as VCam output." },
      { NULL, NULL, NULL, NULL }
  };
  • PyMethondDef 结构的定义
struct PyMethodDef {
      const char  *ml_name;   /* The name of the built-in function/method */
      PyCFunction ml_meth;    /* The C function that <isindex></isindex>mplements it */
      int         ml_flags;   /* Combination of METH_xxx flags, which mostly
                                 describe the args expected by the C func */
      const char  *ml_doc;    /* The __doc__ attribute, or NULL */
  };
  typedef struct PyMethodDef PyMethodDef;

  #define PyCFunction_New(ML, SELF) PyCFunction_NewEx((ML), (SELF), NULL)
  PyAPI_FUNC(PyObject *) PyCFunction_NewEx(PyMethodDef *, PyObject *,
                                           PyObject *);

  /* Flag passed to newmethodobject */
  /* #define METH_OLDARGS  0x0000   -- unsupported now */
  #define METH_VARARGS  0x0001
  #define METH_KEYWORDS 0x0002
  /* METH_NOARGS and METH_O must not be combined with the flags above. */
  #define METH_NOARGS   0x0004
  #define METH_O        0x0008

  /* METH_CLASS and METH_STATIC are a little different; these control
     the construction of methods for a class.  These cannot be used for
     functions in modules. */
  #define METH_CLASS    0x0010
  #define METH_STATIC   0x0020

  /* METH_COEXIST allows a method to be entered even though a slot has
     already filled the entry.  When defined, the flag allows a separate
     method, "__contains__" for example, to coexist with a defined
     slot like sq_contains. */

  #define METH_COEXIST   0x0040
  • 其他没啥好说的结构定义已经很明白了,就是第三个元素ml_falg 需要根据函数时机传入参数要求进行调整 就说几个常用的flag 其他见手册
  • METH_NOARGS 表示没有参数传入,
  • METH_KEYWORDS 表示传入keyword参数
  • METH_VARARGS 表示传入位置参数
  • 部分flag可以组合传入如 METH_VARARGS|METH_KEYWORDS
  • 注意METH_NOARGS 不能与 前面两个flag组合使用
  1. 写类的内置属性信息表说明PyTypeObject实例VCam_ClassInfo
  • 直接上代码,代码中包含了PyTypeObject结构体 大部分元素,具体见object.h 头文件定义
static PyTypeObject VCam_ClassInfo =
  {
      PyVarObject_HEAD_INIT(NULL, 0)
      "PyVcam.VCam",            //可以通过__class__获得这个字符串. CPP可以用类.__name__获取.   const char *
      sizeof(VCam),                 // tp_basicsize 类/结构的长度.调用PyObject_New时需要知道其大小.  Py_ssize_t
      0,                              //tp_itemsize  Py_ssize_t
      (destructor)VCam_Destruct,    //类的析构函数.      destructor
      0,                            //类的print 函数      printfunc
      0,                               //类的getattr 函数  getattrfunc
      0,                              //类的setattr 函数   setattrfunc
      0,                              //formerly known as tp_compare(Python 2) or tp_reserved (Python 3)  PyAsyncMethods *
      0,          //tp_repr 内置函数调用。    reprfunc
      0,                              //tp_as_number   指针   PyNumberMethods *
      0,                              //tp_as_sequence 指针   PySequenceMethods *
      0,                              // tp_as_mapping 指针  PyMappingMethods *
      0,                              // tp_hash   hashfunc
      0,                              //tp_call     ternaryfunc
      0,          //tp_str/print内置函数调用.   reprfunc
      0,                          //tp_getattro    getattrofunc
      0,                          //tp_setattro     setattrofunc
      0,                          //tp_as_buffer 指针 Functions to access object as input/output buffer   PyBufferProcs
      Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,                 //tp_flags    如果没有提供方法的话,为Py_TPFLAGS_DEFAULE     unsigned long
      "VCam Module write by C++!",                   // tp_doc  __doc__,类/结构的DocString.  const char *
      0,                                                   //tp_traverse    call function for all accessible objects    traverseproc
      0,                                                      // tp_clear   delete references to contained objects    inquiry
      0,                                                      //  tp_richcompare   richcmpfunc
      0,                                                      //tp_weaklistoffset  Py_ssize_t
      0,                                                      // tp_iter  getiterfunc
      0,                                          //tp_iternext    iternextfunc


      /* Attribute descriptor and subclassing stuff */

      VCam_MethodMembers,        //类的所有方法集合.   PyMethodDef *
      VCam_DataMembers,          //类的所有数据成员集合.  PyMemberDef *
      0,                                              // tp_getset   PyGetSetDef *
      0,                                              //  tp_base   _typeobject *
      0,                                              //  tp_dict     PyObject *
      0,                                              // tp_descr_get   descrgetfunc
      0,                                                          //tp_descr_set   descrsetfunc
      0,                                              //tp_dictoffset   Py_ssize_t
      (initproc)VCam_init,      //类的构造函数.tp_init       initproc
      0,                      //tp_alloc  allocfunc
      0,                          //tp_new   newfunc
      0,                        // tp_free    freefunc
      0,                      //  tp_is_gc     inquiry
  };
  • VCam_ClassInfo 中把前面所创建的 init函数、析构函数、方法表、成员表等加入类信息表

模块创建和初始化

  1. 创建模块信息
  • 直接上代码
static PyModuleDef ModuleInfo =
   {
   PyModuleDef_HEAD_INIT,
   "PyVcam",               //模块的内置名--__name__.
   NULL,                 //模块的DocString.__doc__
   -1,
   NULL, NULL, NULL, NULL, NULL
   };
  • PyModuleDef 结构题定义
typedef struct PyModuleDef{
    PyModuleDef_Base m_base;
    const char* m_name;
    const char* m_doc;
    Py_ssize_t m_size;
    PyMethodDef *m_methods;
    struct PyModuleDef_Slot* m_slots;
    traverseproc m_traverse;
    inquiry m_clear;
    freefunc m_free;
  } PyModuleDef;
  1. 初始化模块
  • 先上代码
PyMODINIT_FUNC PyInit_PyVcam(void)       //模块外部名称为--PyVcam
  {
      
      Gdiplus::GdiplusStartupInput StartupInput;
      GdiplusStartup(&m_gdiplusToken, &StartupInput, NULL);

      PyObject* pReturn = 0;
      VCam_ClassInfo.tp_new = PyType_GenericNew;       //此类的new内置函数—建立对象.
      
      
      if (PyType_Ready(&VCam_ClassInfo) < 0)
          return NULL;

      pReturn = PyModule_Create(&ModuleInfo);
      if (pReturn == NULL)
          return NULL;
      Py_INCREF(&VCam_ClassInfo);
      PyModule_AddObject(pReturn, "VCam", (PyObject*)&VCam_ClassInfo); //将这个类加入到模块的Dictionary中.
      
      return pReturn;
  }
  • 代码解释:
  • Python模块必须要导出一个返回值为PyObject*名为PyInit_XXX的函数用来初始化模块信息,Python加载模块时候回去直接调用此函数来初始化。
  • PyMODINIT_FUNC 宏其实就是以下语句: __declspec(dllexport) PyObject*
  • VCam_ClassInfo.tp_new = PyType_GenericNew; 这条语句其实可以不用写直接在前面VCam_Classinfo里面对应位置加入PyType_GenericNew 即可,想想找到对应那个置要找到眼花 干脆直接以这种形式写出来;反之前面整个VCam_Classinfo 后面的结构其实可以不写,直接以VCam_Classinfo.xxx =xxx的形式写出
  • 调用一个PyType_Ready (&VCam_ClassInfo)来完成类的定义
  • 然后用PyModule_Create(&ModuleInfo) 创建Module
  • 调用PyModule_AddObject将 Vcam类加入到Module 中 同时别忘了增加类体引用计数
  • 将模块返回给Python 大功告成

PythonC扩展的执行效率问题(GIL)

  1. GIL问题
  • GIL锁原理
for (;;) {
      if (--ticker < 0) {   //这是之前版本的GIL锁原理 执行check_interval条数指令 放一次GIL 貌似现在版本不再按照指令条数来放锁了而是按照时间间隔
          ticker = check_interval;             
          /* Give another thread a chance */
          PyThread_release_lock(interpreter_lock);   //释放 GIL         
          /* Other threads may run now */             
          PyThread_acquire_lock(interpreter_lock, 1); //立马重新申请GIL 一放一抢 其他线程就有机会
      }             
      bytecode = *next_instr++;  //这里读入python指令
      switch (bytecode) {  
          /* execute the next instruction ... */  //执行指令
      }
  }
  • 由于CPython GIL存在在进行多线程任务时 python指令在执行时会一直占着GIL导致其他线程一直在等着抢锁 于是多线程就编程了单线程,无论你开多少个线程貌似都只能同时有一个线程在运行
  1. GIL锁问题的解决
    在纯Python环境下CPython的GIL貌似无解了,但是GIL真的无解了么?
  • 大家都知道IO密集型场景利用多线程能显著提高执行效率,也就是说IO任务执行过程中释放了GIL 显然这个释放肯定不是在ticker<0时释放的, IO任务到底是怎么释放GIL的呢
  • IO任务释放原理如下
/* s.connect((host, port)) method */
  static PyObject *
  sock_connect(PySocketSockObject *s, PyObject *addro)
  {
      sock_addr_t addrbuf;
      int addrlen;
      int res;
   
      /* convert (host, port) tuple to C address */
      getsockaddrarg(s, addro, SAS2SA(&addrbuf), &addrlen);
   
      Py_BEGIN_ALLOW_THREADS
      res = connect(s->sock_fd, addr, addrlen);
      Py_END_ALLOW_THREADS
   
      /* error handling and so on .... */
  }
  • 上面是部分socket代码,可以看到在执行 connect之前 调用了一个宏 Py_BEGIN_ALLOW_THREADS 这个宏就是用来释放GIL的 成功connect后又调用 Py_END_ALLOW_THREADS重新申请GIL
  • 解决Python多线程运行效率问题似乎有门
  • Python计算密集型不能用多线程?似乎利用C++写一个模块来处理计算任务多线程照样能达到并行效果
  • 下面就开始写代码验证这个问题
  • 在c++模块中写了两个计算密集型函数,函数计算返回之类的算法都没有区别唯一区别就是: 其中一个函数在高密度计算前释放了GIL计算完成后重新申请锁
static PyObject* Gil_free(GilTest* self,PyObject* args){    
   LONGLONG num;
   if (!PyArg_ParseTuple(args, "L", &num))return NULL;
   
   LONGLONG rst;
   Py_BEGIN_ALLOW_THREADS
       for (LONGLONG i = 1; i <= num * 100; i++)
       {
           for (LONGLONG j = 1; j <= num * 100; j++)
           {
               rst = i*j;
           }
       }                
   Py_END_ALLOW_THREADS
       return Py_BuildValue("i", rst);
}

static PyObject* Gil_lock(GilTest* self, PyObject* args){

LONGLONG num;

if (!PyArg_ParseTuple(args, "L", &num))return NULL;

LONGLONG rst;

for (LONGLONG i = 1; i <= num * 100; i++)

{

for (LONGLONG j = 1; j <= num * 100; j++)

{

rst = i*j;

}

}

return Py_BuildValue("i", rst);

}
  • 将函数封装到一个python模块中调用模块 写一个脚本开多线程执行
from GilTest import GilTest
  import time
  from threading import Thread


  def foo(num, i, start):
      obj = GilTest()
      obj.compute_with_gil(num)   # 调用的函数计算时没有释放GIL
      print("foo %s is over" % i, time.time() - start)


  def bar(num, i, start):
      obj = GilTest()
      obj.compute_without_gil(num)   # 调用的函数计算时释放GIL
      print("bar %s is over" % i, time.time() - start)


  def run():
      print("stat foo")
      start = time.time()  # 开foo线程开始计时
      thread_list1 = []
      for i in range(10):
          thread_list1.append(Thread(target=foo, args=(1000, i, start)))
      for i in thread_list1:
          i.start()
      for i in thread_list1:
          i.join()
      print("stat bar")
      time.sleep(1) 
      start = time.time()   # 开bar线程开始计时
      thread_list2 = []
      for i in range(10):
          thread_list2.append(Thread(target=bar, args=(1000, i, start)))
      for i in thread_list2:
          i.start()
      for i in thread_list2:
          i.join()


  if __name__ == '__main__':

      run()
  • 输出执行结果
stat foo
  foo 0 is over 2.2932560443878174
  foo 1 is over 4.577575445175171
  foo 2 is over 6.859208583831787
  foo 3 is over 9.145148277282715
  foo 4 is over 11.43115520477295
  foo 5 is over 13.71883225440979
  foo 6 is over 15.999829292297363
  foo 7 is over 18.281397581100464
  foo 8 is over 20.57776975631714
  foo 9 is over 22.851707935333252
  stat bar
  bar 3 is over 4.594241380691528
  bar 6 is over 4.594241380691528
  bar 7 is over 4.609868288040161
  bar 2 is over 4.63910174369812
  bar 8 is over 5.750362157821655
  bar 4 is over 5.765988826751709
  bar 5 is over 5.859748840332031
  bar 0 is over 5.859748840332031
  bar 1 is over 5.859748840332031
  bar 9 is over 5.937881946563721

  Process finished with exit code 0
  • 可以发现foo线程完全像是在运行单线程,每个线程执行完成时间比上一个线程大约多2.3秒看;而bar线程是真正的多线程 ,线程完成计算时间差别很小,而且完成先后顺序是乱序的,因为CPU是四核的所以线程之间还是会存在抢cpu情况,每个线程运行时间较foo要长一点(foo每个线程的运算几乎是独占运行)
  • 再来看看CPU占用
  • python 自建模块 No module named python写模块_Python

python 自建模块 No module named python写模块_Python_02

在执行foo时python进程占用CPU约15%作用,当程序执行到bar线程时可以看到python进程cpu占用直线上飙到接近100%的占用,这也说明了此时python的线程是并行的。

  1. 释放GIL的注意事项
    前面演示似乎解决python多线程GIL问题,执行效率很高,是不是意味着不论啥玩意拿过来直接释放GIL就好了呢,显然不可能是这样的,考虑下GIL为啥存在吧,在c++中释放GIL需要考虑以下问题:
  • 首先要预估函数执行需要多少时间(需要几个周期),若代码几部就能走完那么如果此时释放GIL不但不能提高效率反而会降低代码执行效率,因为释放和申请GIL也是有系统开销的
  • 如果C++代码存在读取或操作PyObject的代码那么在执行这些代码时必须确保线程持有GIL锁
  • 若在函数内释放了GIL锁函数返回前必须重新申请GIL锁,否则将带来灾难性后果

如何用C++ 写Python模块扩展(一)模块C++源码下载 好了先写到这请期待后续的大招 封装windows系统的com组件到python模块