精华内容
下载资源
问答
  • C/C++ 多线程调用嵌入Python完整流程

    千次阅读 2018-08-26 00:25:16
    C/C++ 多线程调用嵌入Python 最近都很忙,忙着把公司的Python回测框架完成。前两天公司同事抱怨 C/C++调用Python超级烦人,动不动就返回NULL,而且内存暴涨,于是我决定尝试解决这个问题,提供一套完整的开发流程,...

    最近都很忙,忙着把公司的Python回测框架完成。前两天公司同事抱怨 C/C++调用Python超级烦人,动不动就返回NULL,而且内存暴涨,于是我决定尝试解决这个问题,提供一套完整的开发流程,供大家技术分享。要完成C/C++调用Python最好是熟悉C/C++和Python,否则出了问题就比较难解决。



    之前没有使用过 C/C++ 调用嵌入Python,只用过 Cython 编写Python 扩展程序。基本是从零开始学习,但我并不想快速完成任务,否则随便度娘一下就OK,事实上,那样做虽能快速解决问题,但只知其一不知其二还是比较心虚。于是从官方文档开始看起。

    Visual Studio / Python 环境搭建

    在各大操作系统上安装Python时,同时安装了 C++开发资源,包括: Include文件,静态链接库文件,动态链接库文件。标准的官方文档包含了 Python/C API 以及 Extending and Embedding 主题文档。

    VS2015+Python3.6.1, 我直接用我所建立的工程来讲解,请根据自己实际情况修改。

    • 官网下载安装 Python3 相应版本
    • 官网下载 python-3.6.1-embed-amd64.zip 文件
      解压之后拷贝到工程生成exe所在目录, 注意python.exe 与生成exe目录同级。
    • VS新建项目, 设置项目 Python 头文件路径
      配置属性>C/C++>常规>附加包含目录 你的Python安装目录\include , 比如我的:
    D:\CodeTool\Python\Python36\include
    • 复制 python36.lib 到 cpp 文件所在目录,设置项目属性方式设置 lib 路径
    D:\CodeTool\Python\Python36\libs\python36.lib
    • 修改 pyconfig.h 文件,Debug 工程不会提示找不到 python36_d.lib

    line 337 左右, 增加 //

    #ifdef _DEBUG
    //# define Py_DEBUG
    #endif

    line 292 左右 ,修改 python36_d.lib

    #           if defined(_DEBUG)
    #               pragma comment(lib,"python36.lib")
    //#             pragma comment(lib,"python36_d.lib")
    #           elif defined(Py_LIMITED_API)
    #               pragma comment(lib,"python3.lib")
    #           else
    #               pragma comment(lib,"python36.lib")
    #           endif /* _DEBUG */

    C++调用Python的接口示例

    test1.cpp
    通过 #pragma comment 指令引入 lib 库

    #include <Python.h>
    
    #pragma comment(lib, "python36.lib")
    
    void test_use_multi_param()
    {
        PyObject *use_int, *use_str, *use_byte, *use_tuple;
        PyObject *use_list, *use_dict, *use_complex;
        PyObject *pName, *pModule, *pFunc;
        PyObject *pArgs, *pValue;
        const char* module_name = "multiply";
        const char* module_method = "test_use_mulit_params";
    
        Py_Initialize();
    
        use_int = Py_BuildValue("i", 123);
        use_str = Py_BuildValue("s", "hello");
        use_byte = Py_BuildValue("y", "hello2");
        use_tuple = Py_BuildValue("(iis)", 1, 2, "three");
        use_list = Py_BuildValue("[iis]", 1, 2, "three");
        use_dict = Py_BuildValue("{s:i,s:i}", "abc", 123, "def", 456);
        use_complex = Py_BuildValue("[ii{ii}(is){s:i}]", 1,2,3,4,5,"xcxcv","ff",1);
    
        pName = PyUnicode_DecodeFSDefault(module_name);
        pModule = PyImport_Import(pName);
        Py_DECREF(pName);
    
        if (pModule != NULL) 
        {
            pFunc = PyObject_GetAttrString(pModule, module_method);
            if (pFunc && PyCallable_Check(pFunc)) 
            {
                pArgs = PyTuple_New(7);
                PyTuple_SetItem(pArgs, 0, use_int);
                PyTuple_SetItem(pArgs, 1, use_str);
                PyTuple_SetItem(pArgs, 2, use_byte);
                PyTuple_SetItem(pArgs, 3, use_list);
                PyTuple_SetItem(pArgs, 4, use_tuple);
                PyTuple_SetItem(pArgs, 5, use_dict);
                PyTuple_SetItem(pArgs, 6, use_complex);
    
                pValue = PyObject_CallObject(pFunc, pArgs);
                Py_DECREF(pArgs);
                if (pValue != NULL)
                {
                    int ret_int;
                    char *ret_str, *ret_byte;
                    PyObject* ret_list, *ret_tuple, *ret_dict, *ret_complex;
                    //解析元组
                    PyArg_ParseTuple(pValue, "isyOOOO", &ret_int, &ret_str, &ret_byte, &ret_list,&ret_tuple,&ret_dict,&ret_complex);
                    Py_DECREF(pValue);
                }
                else {
                    Py_DECREF(pFunc);
                    Py_DECREF(pModule);
                    PyErr_Print();
                    fprintf(stderr, "Call failed\n");
                }
            }
            else
            {
                if (PyErr_Occurred())
                    PyErr_Print();
                fprintf(stderr, "Cannot find function \"%s\"\n", module_method);
            }
            Py_XDECREF(pFunc);
            Py_DECREF(pModule);
        }
        else
        {
            PyErr_Print();
            fprintf(stderr, "Failed to load \"%s\"\n", module_name);
        }
        Py_FinalizeEx();
    }
    
    
    int main(int argc, char *argv[])
    {
        test_use_multi_param();
        system("pause");
    }

    这是一个调用 Python 函数的基本用法,其中包含了几个阶段:

    • Py_Initialize - Py_FinalizeEx
    • Py模块加载,Py函数加载,Py函数参数构造,调用Py函数,获取Py函数返回,
    • 变量引用计数处理/ 错误处理

    变量引用计数管理,请直接参考 引用计数
    C/C++ 使用Python对象,对于引用计数一定要如履薄冰,否则就会出现内存泄漏。

    C++多线程调用嵌入Python

    在我们公司里,C++程序会运行嵌入Pyhton作为扩展接口。在C++多线程环境下,直接调用 api操作 Python解释器,肯定会导致 core dump, 因为 Python 绝大部分函数都是非线程安全的。由GIL控制访问顺序。

    启用线程支持

    Py_Initialize();
    PyEval_InitThreads();
    // 其它代码
    Py_FinalizeEx();

    编译解释器库时启用了多线程支持(VS默认支持),才能使用 PyEval_InitThreads, 如果你的程序不需要多线程,那么建议关闭多线程支持。

    线程状态与全局解释器锁(GIL)

    Python解释器不是完全线程安全的。为了支持多线程Python程序,有一个全局锁,称为 global interpreter lock or GIL,在当前线程能够安全访问Python对象之前,它必须由当前线程持有。没有锁,即使是最简单的操作也可能导致多线程程序中的问题:例如,当两个线程同时增加相同对象的引用计数时,引用计数可能最终只增加一次,而不是增加两次。

    因此,存在这样的规则,即只有获取了GIL的线程可以操作Python对象或调用Python/C API函数。为了模拟执行的并发性,解释程序经常尝试切换线程(参见sys.setswitchinterval())。该锁还围绕可能阻塞I/O操作(如读取或写入文件)释放,以便其他Python线程可以同时运行。

    Python解释器将一些特定于线程的簿记信息保存在称为PyThreadState的数据结构内。还有一个全局变量指向当前的PyThreadState状态:它可以使用PyThreadState_Get()检索。

    参考自:https://docs.python.org/3/c-api/init.html

    从扩展代码执行释放GIL

    Py_BEGIN_ALLOW_THREADS
    ... Do some blocking I/O operation ...
    Py_END_ALLOW_THREADS

    以上宏实际展开

    PyThreadState *_save
    _save = PyEval_SaveThread()
    ...Do some blocking I/O operation...
    PyEval_RestoreThread(_save)

    非Python创建的线程

    如果需要从第三方即非Python创建线程调用Python代码(通常这将是上述第三方库提供的回调API的一部分),则必须首先通过创建线程状态数据结构来向解释器注册这些线程,然后获取GIL,最后存储它们的线程状态指针,然后可以开始使用Python /C API。完成后,您应该重置线程状态指针,释放GIL,并最终释放线程状态数据结构。

    PyGILState_Ensure()和PyGILState_Release()函数自动执行上述所有操作。从C线程调用Python的典型习惯用法是:

    PyGILState_STATE gstate;
    gstate = PyGILState_Ensure();
    
    /* Perform Python actions here. */
    result = CallSomeFunction();
    /* evaluate result or handle exception */
    
    /* Release the thread. No Python API allowed beyond this point. */
    PyGILState_Release(gstate);

    注意:PyGILState_xx()函数假设只有一个全局解释器(由Py_Initialize()自动创建)。Python支持创建额外的解释器(使用Py_NewInterpreter()),但不支持混合多个解释器和PyGILState_xx() API。

    参考自:https://docs.python.org/3/c-api/init.html

    根据上面官方文档,就可以轻易写出相关代码了。

    // 封装PyGILState_Ensure/PyGILState_Release
    class PythonThreadLocker
    {
        PyGILState_STATE state;
    public:
        PythonThreadLocker() : state(PyGILState_Ensure())
        {}
        ~PythonThreadLocker() {
            PyGILState_Release(state);
        }
    };
    
    int CallSomeFunction()
    {
        int argc = 5;
        char *argv[] = { "", "multiply", "multiply", "3", "2" };
        PyObject *pName, *pModule, *pFunc;
        PyObject *pArgs, *pValue;
        int i;
    
        pName = PyUnicode_DecodeFSDefault(argv[1]);
        /* Error checking of pName left out */
    
        pModule = PyImport_Import(pName);
        Py_DECREF(pName);
    
        if (pModule != NULL) {
            pFunc = PyObject_GetAttrString(pModule, argv[2]);
            /* pFunc is a new reference */
    
            if (pFunc && PyCallable_Check(pFunc)) {
                pArgs = PyTuple_New(argc - 3);
                for (i = 0; i < argc - 3; ++i) {
                    pValue = PyLong_FromLong(atoi(argv[i + 3]));
                    if (!pValue) {
                        Py_DECREF(pArgs);
                        Py_DECREF(pModule);
                        fprintf(stderr, "Cannot convert argument\n");
                        return 1;
                    }
                    /* pValue reference stolen here: */
                    PyTuple_SetItem(pArgs, i, pValue);
                }
                pValue = PyObject_CallObject(pFunc, pArgs);
                Py_DECREF(pArgs);
                if (pValue != NULL) {
                    printf("Result of call: %ld\n", PyLong_AsLong(pValue));
                    Py_DECREF(pValue);
                }
                else {
                    Py_DECREF(pFunc);
                    Py_DECREF(pModule);
                    PyErr_Print();
                    fprintf(stderr, "Call failed\n");
                    return 1;
                }
            }
            else {
                if (PyErr_Occurred())
                    PyErr_Print();
                fprintf(stderr, "Cannot find function \"%s\"\n", argv[2]);
            }
            Py_XDECREF(pFunc);
            Py_DECREF(pModule);
        }
        else {
            PyErr_Print();
            fprintf(stderr, "Failed to load \"%s\"\n", argv[1]);
            return 1;
        }
    
        return 0;
    }
    
    
    void use_thread_a()
    {
        PythonThreadLocker locker;
        int result = CallSomeFunction();
    }
    
    
    // 创建线程
    int main(int argc, char *argv[])
    {
        Py_Initialize();
        PyEval_InitThreads();
    
        printf("%d", PyEval_ThreadsInitialized());
        printf("a%d\n", PyGILState_Check());
    
        Py_BEGIN_ALLOW_THREADS
            printf("b%d\n", PyGILState_Check());
            std::thread t1(use_thread_a);
            std::thread t2(use_thread_a);
            std::thread t3(use_thread_a);
            std::thread t4(use_thread_a);
            std::thread t5(use_thread_a);
            t1.join();
            t2.join();
            t3.join();
            t4.join();
            t5.join();
            printf("c%d\n", PyGILState_Check());
        Py_END_ALLOW_THREADS
            printf("d%d\n", PyGILState_Check());
            CallSomeFunction();
    
        Py_FinalizeEx();
    
        return 0;
    }

    multiply.py 文件

    def multiply(a,b):
        print("Will compute", a, "times", b)
        c = 0
        for i in range(0, a):
            c = c + b
        return c
    
    def hello2():
        print('hello')
    
    
    def tset_use_pd():
        import pandas as pd
        print(pd.DataFrame({'a':[1,2,3],'b':[4,5,6]}))
    
    def test_raise_error():
        raise ValueError('test raise valueerror')
    
    def test_use_mulit_params(use_int, use_str: str, use_byte: bytes, use_list: list, use_tuple: tuple, use_dict: dict, use_complex):
        print('use_int', use_int)
        print('use_str', use_str)
        print('use_byte', use_byte)
        print('use_list', use_list)
        print('use_tuple', use_tuple)
        print('use_dict', use_dict)
        print('use_complex', use_complex)
        return (use_int, use_str, use_byte, use_list, use_tuple, use_dict, use_complex)

    思考

    作为一名前行的软件工程师,需要不断思考学习积累,绝不能急于求成,心浮气躁。随便百度搜索答案。虽然一天只做了一件事,但也是值得的。通过阅读官方文档,分析与实践同行,充分理解其含义,体会深刻。不然永远都不会明白程序为什么会 core dumps, wrong results, mysterious crashes

    加好友 & 工程下载

    如果你和我有共同爱好,加好友一起学习吧!
    这里写图片描述

    展开全文
  • Python作为一门优秀的编程语言,在TIOBE排行榜上,长期稳坐前十的位置。但在日常生活中,尤其是对编程...上周五,“CSDN博客专家会客厅”走进班级,CSDN博客专家、技术达人刘冬作为受邀专家与大家畅聊Python。 我也

    Python作为一门优秀的编程语言,在TIOBE排行榜上,长期稳坐前十的位置。但在日常生活中,尤其是对编程小白来说,想学习一门新语言并不简单,有的甚至只能自学到一点皮毛。

    为此,CSDN特向广大Python爱好者开设了学习班,帮助大家在学习Python的道路上少走弯路,事半功倍。上周五,“CSDN博客专家会客厅”走进班级,CSDN博客专家、技术达人刘冬作为受邀专家与大家畅聊Python。

    我也要加入CSDN Python学习班,==>>猛戳这里

    刘冬:CSDN博客专家、技术达人,拥有7年开发经验,先后做过Fortran/C/Java/PythonJS/Android等开发,创过业,做过技术总监,仍然对技术怀有情怀。 2014年开通CSDN博客,有81篇原创技术文章,访问量22万+。

    下面是来自刘冬在CSDN Python学习班的分享:

    小步慢跑,聊聊Python中的多线程

    在开始今天的话题之前,简单的来看有关Python的体系结构。为了方便起见我做一张导图,让大家有个宏观的认识。

    图片描述

    今天本来准备全面的聊聊有关高性能并发这个话题来着,但是周末马上要来了啊。所以我就取了其中的一点来介绍,关于其他的方面,有兴趣的小伙伴可以和我交流。谈高效并发,往往脱离不了以下三种方案:

    • 进程:每个逻辑控制流都是一个进程,由内核来调度和维护。因为进程有独立的虚拟地址空间,想要和其他控制流通信必须依靠显示的进程间通信,即我们所说的IPC机制
    • 线程:线程应该是我们最为熟知的。它本质是运行在一个单一进程上下文中的逻辑流,由内核进行调度。
    • I/O多路复用:应用程序在一个进程的上下文中显式地调度他们自己的逻辑流.逻辑流被模型化为状态机,数据到达文件描述符之后,主程序显式地从一个状态转换为另一个状态。因为程序都是以一个单独的进程,所以所有的流都共享同一个地址空间。基本的思路就是使用select函数要求内核挂起进程,只有一个或多个I/O事件发生后,才将控制权返回给应用程序。

      看起来令人难以理解,但幸运的是Python中针对这三方面都提供了响应的支持,简化了我们的操作。那今天咱就聊聊其中的一点–线程。为什么选择线程呢?一方面考虑到大部分人都有线程这个概念,另一方面考虑相比进程线程更轻量级,相比协程,线程更易于理解。进程和线程之间的关系可以用衣服最简单的图来表示:

    这里写图片描述

    线程的状态

    任何一门支持线程的语言都可以具备以下几种运行状态,无论是你做Java、Python还是C,首先来看下面一张图:

    这里写图片描述

    在这里我简单来解释以下这几种状态的含义:

    • 新建:使用线程的第一步就是创建线程,创建后的线程只是进入可执行的状态,也就是Runnable

    • Runnable:进入此状态的线程还并未开始运行,一旦CPU分配时间片给这个线程后,该线程才正式的开始运行

    • Running:线程正式开始运行,在运行过程中线程可能会进入阻塞的状态,即Blocked

    • Blocked:在该状态下,线程暂停运行,解除阻塞后,线程会进入Runnable状态,等待CPU再次分配时间片给它

    • 结束:线程方法执行完毕或者因为异常终止返回

    其中最复杂的是线程从Running进入Blocked状态,通常有三种情况:

    • 睡眠:线程主动调用sleep()或join()方法后

    • 等待:线程中调用wait()方法,此时需要有其他线程通过notify()方法来唤醒

    • 同步:线程中获取线程锁,但是因为资源已经被其他线程占用时

    到现在,我们对线程有个基本的概念,光说不练假把式,下面我们就通过是三个小的示例来聊聊线程的使用以及线程中最终的两个概念:同步和通信。

    线程简单使用

    Python当中要实现多线程有两种方式:一种是使用低级的_thread模块,另一种高级threading模块,相比而言,我推荐使用threading模块。另外我只推荐用于多线程用于处理有关I/O的操作,不然反而造成性能下降。在开始之前呢,先来了解下threading模块给我提供哪些常用的类: Thread,Lock,RLock,Condition,Event,Semaphore,Timer和Local。这几个类可谓开发多线程中的神兵利器。但是介于篇幅,咱就不展开讲了。

    我们直接来看如何使用多线程,这才是至关重要的,有句老话是这么说的:要想让小孩子跑得先让他学会走。我们这就走两步:

    import threading
    def run(number): 
    print(threading.currentThread().getName() + '\n') 
    print(number)
    if name == '__main': 
      for i in range(10):
     #创建线程
         mythread = threading.Thread(target=run, args=(i,)) 
         mythread.start()

    多线程的创建和运行都是套路啊,写的多了自然熟了,来看看运行结果:

    Thread-1,value=0 
    Thread-2,value=1 
    Thread-3,value=2 
    Thread-4,value=3 
    Thread-5,value=4 
    Thread-6,value=5 
    Thread-7,value=6 
    Thread-8,value=7 
    Thread-9,value=8 
    Thread-10,value=9 

    同步与通信

    多线程开发中最难的问题不是如何使用,而是如何写出正确高效的代码,要写出正确而高效的代码必须要理解两个很重要的概念:同步和通信。所谓的通信指的是线程之间如何交换消息,而同步则用于控制不同线程之间操作发生的相对顺序。简单点说同步就是控制多个线程访问代码的顺序,通信就是线程之间如何传递消息。在python中实现同步的最简单的方案就是使用锁机制,实现通信最简单的方案就是Event。下面就来看看这两者的具体使用。

    线程同步

    当多个线程同时访问同一资源的时候,就会发生竞争,这有点像很多个男性都在追同一个妹纸一样,结果是不可预期的。因此有必要使用某种机制来保证每个男生都有机会和女生相处,这有点像将小姑娘放在一间房子里,然后进去的男生锁上门。下一个男生要想进去。必须等待上一个男生出来。只不过在这里叫线程锁。

    Python的threading模块为我们提供了线程锁功能,在threading中提供RLock对象,RLock对象内部维护着一个Lock对象,它是一种可重入锁。对于Lock对象而言,如果一个线程连续两次进行acquire操作,那么由于第一次acquire之后没有release,第二次acquire将挂起线程。这会导致Lock对象永远不会release,使得线程死锁。而RLock对象允许一个线程多次对其进行acquire操作,因为在其内部通过一个counter变量维护着线程acquire的次数。而且每一次的acquire操作必须有一个release操作与之对应,在所有的release操作完成之后,别的线程才能申请该RLock对象。

    通过锁机制,最终多线程访问共享资源的过程就类似以下:

    图片描述

    上图其实演示了在使用锁来解决线程同步最本质的一点:将所有线程对共享资源的读写操作串行化。

    import threading
    
    mylock = threading.RLock()
    num = 0
    
    
    class WorkThread(threading.Thread):
        def __init__(self, name):
            threading.Thread.__init__(self)
            self.t_name = name
    
        def run(self):
            global num
            while True:
                mylock.acquire()
                print('\n%s locked, number: %d' % (self.t_name, num))
                if num >= 4:
                    mylock.release()
                    print('\n%s released, number: %d' % (self.t_name, num))
                    break
                num += 1
                print('\n%s released, number: %d' % (self.t_name, num))
                mylock.release()
    
    
    def test():
        thread1 = WorkThread('A-Worker')
        thread2 = WorkThread('B-Worker')
        thread1.start()
        thread2.start()
    
    
    if __name__ == '__main__':
        test() 

    来看看运行结果:

    A-Worker locked, number: 0
    
    A-Worker released, number: 1
    
    A-Worker locked, number: 1
    
    A-Worker released, number: 2
    
    A-Worker locked, number: 2
    
    A-Worker released, number: 3
    
    A-Worker locked, number: 3
    
    A-Worker released, number: 4
    
    A-Worker locked, number: 4
    
    A-Worker released, number: 4
    
    B-Worker locked, number: 4
    
    B-Worker released, number: 4
    

    有些同学会问除了Lock和RLock还有其他的方式来实现类似的效果么?当然,比如Condition和Semaphore都有类似的功能,其中Condition是在Lock/RLock的基础上再次包装而成,而Semaphore的原理和操作系统的PV操作一致。之所以不细说的原因在于基本他们的基本使用和原理并无本质区别。我个人也一直认为越复杂的东西背后越是有简单的原理,当然欢迎有兴趣的同学和我进行探讨。

    线程通信

    在很多时候,我们需要在线程间传递消息,也叫作线程通信。Python中提供的Event就是最简单的通信机制之一。使用threading.Event可以使一个线程等待其他线程的通知,我们把这个Event传递到线程对象中,Event默认内置了一个标志,初始值为False。一旦该线程通过wait()方法进入等待状态,直到另一个线程调用该Event的set()方法将内置标志设置为True时,该Event会通知所有等待状态的线程恢复运行。先来看看Event中一些常用的方法:

    方法名 含义
    isSet() 测试内置的标识是否为True
    set() 将标识设置为True,并通知所有处于阻塞状态的线程恢复运行
    clear() 将标识设置为False
    wait([timeout]) 如果标识为True时立即返回,否则阻塞线程至阻塞状态,等待其他线程调用set()

    来看个简单示例,我们暂且假设你有6个妹纸需要叫她们起床,这时候你该怎么做呢?

    import threading
    import time
    
    
    class WorkThread(threading.Thread):
        def __init__(self, signal):
            threading.Thread.__init__(self)
            self.singal = signal
    
        def run(self):
            print("妹纸 %s,睡觉了 ..." % self.name)
            self.singal.wait()
            print("妹纸 %s, 起床..." % self.name)
    
    
    if __name__ == "__main__":
        singal = threading.Event()
        for t in range(0, 6):
            thread = WorkThread(singal)
            thread.start()
    
        print("三秒钟后叫妹纸起床 ")
        time.sleep(3)
        #唤醒阻塞中的妹纸
        singal.set()

    这里的的你就充当了主线程,每个妹纸就是一个子线程,不出意外三秒之后你就会按时唤醒所有的妹纸了:

    妹纸 Thread-1,睡觉了 ...
    妹纸 Thread-2,睡觉了 ...
    妹纸 Thread-3,睡觉了 ...
    妹纸 Thread-4,睡觉了 ...
    妹纸 Thread-5,睡觉了 ...
    妹纸 Thread-6,睡觉了 ...
    三秒钟后叫妹纸起床 
    妹纸 Thread-1, 起床...
    妹纸 Thread-2, 起床...
    妹纸 Thread-5, 起床...
    妹纸 Thread-4, 起床...
    妹纸 Thread-3, 起床...
    妹纸 Thread-6, 起床...

    使用Event实现线程通信通信固然可以,但是另一种进行线程通信的方式是借助队列,也就是Queue。在python的标准库中提供了线程安全的队列,基于FIFO(先进先出)实现,可以方便的帮助我们实现线程间的消息传递,使用非常简单,其原理也不难,用一张简单的图展示:

    图片描述
    另外,凡是符合该种结构的多线程通信过程我们称之为生产者-消费者模型。

    线程池

    其实,有关多线程的使用时非常简单的,更多的是根据具体的业务情况编写相应的逻辑.初次之外,考虑处理器的资源毕竟是有限的,不能一味的创建线程,我曾看到有些小伙伴在写爬虫的时候,100个url就创建了100个线程,其后果可想而知,因此当有需求要用到很多线程时,考虑使用线程池技术。
    分享结束后,同学们还向刘冬请教了在工作学习中遇到的相关问题,比如

    • 生产者消费者模式是不是类似于发布者订阅者模式?
    • 请问已经存在全局锁了,为什么还会有threading.Lock等? 不明白全局锁的作用是什么
    • 单线程的IO复用(REACTOR)和多线程版的IO复用区别在哪
    • 关于Thread类,target参数,传入的函数如果有返回值如何处理

    如果你是Python爱好者、或者正准备学习Python开发知识,CSDN Python学习班都会敞开双臂欢迎你,别犹豫,赶快加入我们

    详情,请点击:http://geek.csdn.net/news/detail/159636

    展开全文
  • 我们进行程序开发的时候,肯定避免不了要处理并发的情况。...本文基于 Python3 讲解,Python 实现多线程编程需要借助于 threading 模块。 所以,我们要在代码中引用它。 import threading thread...

    我们进行程序开发的时候,肯定避免不了要处理并发的情况。

    一般并发的手段有采用多进程和多线程。

    但线程比进程更轻量化,系统开销一般也更低,所以大家更倾向于用多线程的方式处理并发的情况。

    Python 提供多线程编程的方式。

    本文基于 Python3 讲解,Python 实现多线程编程需要借助于 threading 模块。

    所以,我们要在代码中引用它。

    import threading
    

    threading 模块中最核心的内容是 Thread 这个类。

    我们要创建 Thread 对象,然后让它们运行,每个 Thread 对象代表一个线程,在每个线程中我们可以让程序处理不同的任务,这就是多线程编程。

    值得注意的是,程序运行时默认就是在主线程上

    创建 Thread 对象有 2 种手段。

    1. 直接创建 Thread ,将一个 callable 对象从类的构造器传递进去,这个 callable 就是回调函数,用来处理任务。
    2. 编写一个自定义类继承 Thread,然后复写 run() 方法,在 run() 方法中编写任务处理代码,然后创建这个 Thread 的子类。

    1. 直接创建 Thread 对象。

    class threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
    

    Thread 的构造方法中,最重要的参数是 target,所以我们需要将一个 callable 对象赋值给它,线程才能正常运行。

    如果要让一个 Thread 对象启动,调用它的 start() 方法就好了。

    下面是代码示例。

    import threading
    import time
    
    def test():
    
        for i in range(5):
            print('test ',i)
            time.sleep(1)
    
    
    thread = threading.Thread(target=test)
    thread.start()
    
    for i in range(5):
        print('main ', i)
        time.sleep(1)
    

    上面代码很简单,在主线程上打印 5 次,在一个子线程上打印 5 次。

    运行结果如下:

    test  0
    main  0
    main  1
    test  1
    main  2
    test  2
    main  3
    test  3
    main  4
    test  4
    

    上面的 callable 没有参数,如果需要传递参数的话,args 是固定参数,kwargs 是可变参数。

    Thread 的名字

    每一个 Thread 都有一个 name 的属性,代表的就是线程的名字,这个可以在构造方法中赋值。

    如果在构造方法中没有个 name 赋值的话,默认就是 “Thread-N” 的形式,N 是数字。

    import threading
    import time
    
    def test():
    
        for i in range(5):
            print(threading.current_thread().name+' test ',i)
            time.sleep(1)
    
    
    thread = threading.Thread(target=test)
    thread.start()
    
    for i in range(5):
        print(threading.current_thread().name+' main ', i)
        time.sleep(1)
    

    通过 thread.current_thread() 方法可以返回线程本身,然后就可以访问它的 name 属性。

    上面代码运行结果如下:

    Thread-1 test  0
    MainThread main  0
    Thread-1 test  1
    MainThread main  1
    Thread-1 test  2
    MainThread main  2
    Thread-1 test  3
    MainThread main  3
    Thread-1 test  4
    MainThread main  4
    

    如果我们在 Thread 对象创建时,构造方法里面赋值。

    thread = threading.Thread(target=test,name='TestThread')
    

    那么,运行结果会变成这个样子。

    TestThread test  0
    MainThread main  0
    MainThread main  1
    TestThread test  1
    MainThread main  2
    TestThread test  2
    MainThread main  3
    TestThread test  3
    MainThread main  4
    TestThread test  4
    

    Thread 的生命周期

    1. 创建对象时,代表 Thread 内部被初始化。
    2. 调用 start() 方法后,thread 会开始运行。
    3. thread 代码正常运行结束或者是遇到异常,线程会终止。

    可以通过 Thread 的 is_alive() 方法查询线程是否还在运行。

    值得注意的是,is_alive() 返回 True 的情况是 Thread 对象被正常初始化,start() 方法被调用,然后线程的代码还在正常运行。

    import threading
    import time
    
    def test():
    
        for i in range(5):
            print(threading.current_thread().name+' test ',i)
            time.sleep(0.5)
    
    
    thread = threading.Thread(target=test,name='TestThread')
    # thread = threading.Thread(target=test)
    thread.start()
    
    for i in range(5):
        print(threading.current_thread().name+' main ', i)
        print(thread.name+' is alive ', thread.isAlive())
        time.sleep(1)
    

    在上面的代码中,我让 TestThread 比 MainThread 早一点结束,代码运行结果如下。

    TestThread test  0
    MainThread main  0
    TestThread is alive  True
    TestThread test  1
    MainThread main  1
    TestThread is alive  True
    TestThread test  2
    TestThread test  3
    MainThread main  2
    TestThread is alive  True
    TestThread test  4
    MainThread main  3
    TestThread is alive  False
    MainThread main  4
    TestThread is alive  False
    

    我们可以看到,主线程通过调用 TestThread 的 isAlive() 方法,准确查询到了它的存货状态。

    join() 提供线程阻塞手段。

    上面代码两个线程是同时运行的,但如果让一个先运行,一个后运行,怎么做呢?

    调用一个 Thread 的 join() 方法,可以阻塞自身所在的线程。

    import threading
    import time
    
    def test():
    
        for i in range(5):
            print(threading.current_thread().name+' test ',i)
            time.sleep(0.5)
    
    
    thread = threading.Thread(target=test,name='TestThread')
    thread.start()
    thread.join()
    
    for i in range(5):
        print(threading.current_thread().name+' main ', i)
        print(thread.name+' is alive ', thread.isAlive())
        time.sleep(1)
    

    主线程创建了 TestThread 对象后,让其 start,然后通过调用 join() 方法,实现等待。程序运行结果如下:

    TestThread test  0
    TestThread test  1
    TestThread test  2
    TestThread test  3
    TestThread test  4
    MainThread main  0
    TestThread is alive  False
    MainThread main  1
    TestThread is alive  False
    MainThread main  2
    TestThread is alive  False
    MainThread main  3
    TestThread is alive  False
    MainThread main  4
    TestThread is alive  False
    

    默认的情况是,join() 会一直等待对应线程的结束,但可以通过参数赋值,等待规定的时间就好了。

    def join(self, timeout=None):
    

    timeout 是一个浮点参数,单位是秒。

    如果我们更改上面的代码。

    thread.join(1.0)
    

    它的结果会是这样。

    TestThread test  0
    TestThread test  1
    MainThread main  0
    TestThread is alive  True
    TestThread test  2
    TestThread test  3
    MainThread main  1
    TestThread is alive  True
    TestThread test  4
    MainThread main  2
    TestThread is alive  False
    MainThread main  3
    TestThread is alive  False
    MainThread main  4
    TestThread is alive  False
    

    主线程只等待了 1 秒钟。

    Thread 中的 daemon 属性

    有同学可能会注意到,Thread 的构造方法中有一个 daemon 参数。默认是 None。

    那么,daemon 起什么作用呢?

    我们先看一段示例代码。

    import threading
    import time
    
    def test():
    
        for i in range(5):
            print(threading.current_thread().name+' test ',i)
            time.sleep(2)
    
    
    thread = threading.Thread(target=test,name='TestThread')
    # thread = threading.Thread(target=test,name='TestThread',daemon=True)
    thread.start()
    
    
    for i in range(5):
        print(threading.current_thread().name+' main ', i)
        print(thread.name+' is alive ', thread.isAlive())
        time.sleep(1)
    

    我们让主线程执行代码的时长比 TestThread 要短。

    程序运行结果如下。

    TestThread test  0
    MainThread main  0
    TestThread is alive  True
    MainThread main  1
    TestThread is alive  True
    TestThread test  1
    MainThread main  2
    TestThread is alive  True
    MainThread main  3
    TestThread is alive  True
    TestThread test  2
    MainThread main  4
    TestThread is alive  True
    TestThread test  3
    TestThread test  4
    

    MainThread 没有代码运行的时候,TestThread 还在运行。

    这是因为 MainThread 在等待其他线程的结束。

    TestThread 中 daemon 属性默认是 False,这使得 MainThread 需要等待它的结束,自身才结束。

    如果要达到,MainThread 结束,子线程也立马结束,怎么做呢?

    其实很简单,只需要在子线程调用 start() 方法之前设置 daemon 就好了。

    当然也可以在子线程的构造器中传递 daemon 的值为 True。

    thread = threading.Thread(target=test,name='TestThread',daemon=True)
    # thread.setDaemon(True)
    

    更改前面代码示例,运行结果如下

    TestThread test  0
    MainThread main  0
    TestThread is alive  True
    MainThread main  1
    TestThread is alive  True
    TestThread test  1
    MainThread main  2
    TestThread is alive  True
    MainThread main  3
    TestThread is alive  True
    TestThread test  2
    MainThread main  4
    TestThread is alive  True
    

    可以看到 MainThread 结束了 TestThread 也结束了。

    2.自定义类继承 Thread

    前面讲过,直接初始化一个 Thread,然后,现在还有一种方式就是自定义一个 Thread 的子类,然后复写它的 run() 方法。

    import threading
    import time
    
    
    class TestThread(threading.Thread):
    
        def __init__(self,name=None):
            threading.Thread.__init__(self,name=name)
    
        def run(self):
            for i in range(5):
                print(threading.current_thread().name + ' test ', i)
                time.sleep(1)
    
    
    thread = TestThread(name='TestThread')
    thread.start()
    
    
    for i in range(5):
        print(threading.current_thread().name+' main ', i)
        print(thread.name+' is alive ', thread.isAlive())
        time.sleep(1)
    

    上面的代码,我们自定义了 TestThread 这个类,然后继承了 threading.Thread。

    只有在 run() 方法中处理逻辑。最终代码运行结果如下:

    TestThread test  0
    MainThread main  0
    TestThread is alive  True
    TestThread test  1
    MainThread main  1
    TestThread is alive  True
    TestThread test  2
    MainThread main  2
    TestThread is alive  True
    MainThread main  3
    TestThread is alive  True
    TestThread test  3
    MainThread main  4
    TestThread test  4
    TestThread is alive  True
    

    这与之前的效果并无差异,但我还是推荐用这种方法,毕竟面向对象编程嘛。

    自此,Python 多线程编码技术就大致介绍完毕,大家可以进行实际代码编写了。

    但是,多线程编程的难点在于多个线程之间共享数据的同步,这是非常容易出错的地方,我将分别编写相应的博文去介绍一些高级的技术点。

    展开全文
  • 这篇文章主要介绍了python爬虫开发之使用Python爬虫库requests多线程抓取猫眼电影TOP100实例,需要的朋友可以参考下 使用Python爬虫库requests多线程抓取猫眼电影TOP100思路: 查看网页源代码 抓取单页内容 正则...

    这篇文章主要介绍了python爬虫开发之使用Python爬虫库requests多线程抓取猫眼电影TOP100实例,需要的朋友可以参考下
    使用Python爬虫库requests多线程抓取猫眼电影TOP100思路:

    查看网页源代码
    抓取单页内容
    正则表达式提取信息
    猫眼TOP100所有信息写入文件
    多线程抓取
    运行平台:windows
    Python版本:Python 3.7.
    IDE:Sublime Text
    浏览器:Chrome浏览器
    1.查看猫眼电影TOP100网页原代码
    按F12查看网页源代码发现每一个电影的信息都在“

    ”标签之中。在这里插入图片描述
    点开之后,信息如下:在这里插入图片描述
    2.抓取单页内容
    在浏览器中打开猫眼电影网站,点击“榜单”,再点击“TOP100榜”如下图:在这里插入图片描述
    接下来通过以下代码获取网页源代码:

    #-*-coding:utf-8-*-
    import requests
    from requests.exceptions import RequestException
      
    #猫眼电影网站有反爬虫措施,设置headers后可以爬取
    headers = {
        'Content-Type': 'text/plain; charset=UTF-8',
        'Origin':'https://maoyan.com',
        'Referer':'https://maoyan.com/board/4',
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
        }
      
    #爬取网页源代码
    def get_one_page(url,headers):
        try:
            response =requests.get(url,headers =headers)
            if response.status_code == 200:
                return response.text
            return None
        except RequestsException:
            return None
      
    def main():
        url = "https://maoyan.com/board/4"
        html = get_one_page(url,headers)
        print(html)
      
    if __name__ == '__main__':
        main()
    

    执行结果如下:在这里插入图片描述
    3.正则表达式提取信息
    上图标示信息即为要提取的信息,代码实现如下:

    #-*-coding:utf-8-*-
    import requests
    import re
    from requests.exceptions import RequestException
      
    #猫眼电影网站有反爬虫措施,设置headers后可以爬取
    headers = {
        'Content-Type': 'text/plain; charset=UTF-8',
        'Origin':'https://maoyan.com',
        'Referer':'https://maoyan.com/board/4',
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
        }
      
    #爬取网页源代码
    def get_one_page(url,headers):
        try:
            response =requests.get(url,headers =headers)
            if response.status_code == 200:
                return response.text
            return None
        except RequestsException:
            return None
      
    #正则表达式提取信息
    def parse_one_page(html):
        pattern = re.compile('<dd>.*?board-index.*?>(\d+)</i>.*?data-src="(.*?)".*?name"><a'
            +'.*?>(.*?)</a>.*?star">(.*?)</p>.*?releasetime">(.*?)</p>.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>',re.S)
        items = re.findall(pattern,html)
        for item in items:
            yield{
            'index':item[0],
            'image':item[1],
            'title':item[2],
            'actor':item[3].strip()[3:],
            'time':item[4].strip()[5:],
            'score':item[5]+item[6]
            }
      
    def main():
        url = "https://maoyan.com/board/4"
        html = get_one_page(url,headers)
        for item in parse_one_page(html):
            print(item)
      
    if __name__ == '__main__':
        main()
    

    执行结果如下:在这里插入图片描述
    4.猫眼TOP100所有信息写入文件
    上边代码实现单页的信息抓取,要想爬取100个电影的信息,先观察每一页url的变化,点开每一页我们会发现url进行变化,原url后面多了‘?offset=0’,且offset的值变化从0,10,20,变化如下:在这里插入图片描述
    在这里插入图片描述
    代码实现如下:

    #-*-coding:utf-8-*-
    import requests
    import re
    import json
    import os
    from requests.exceptions import RequestException
      
    #猫眼电影网站有反爬虫措施,设置headers后可以爬取
    headers = {
        'Content-Type': 'text/plain; charset=UTF-8',
        'Origin':'https://maoyan.com',
        'Referer':'https://maoyan.com/board/4',
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
        }
      
    #爬取网页源代码
    def get_one_page(url,headers):
        try:
            response =requests.get(url,headers =headers)
            if response.status_code == 200:
                return response.text
            return None
        except RequestsException:
            return None
      
    #正则表达式提取信息
    def parse_one_page(html):
        pattern = re.compile('<dd>.*?board-index.*?>(\d+)</i>.*?data-src="(.*?)".*?name"><a'
            +'.*?>(.*?)</a>.*?star">(.*?)</p>.*?releasetime">(.*?)</p>.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>',re.S)
        items = re.findall(pattern,html)
        for item in items:
            yield{
            'index':item[0],
            'image':item[1],
            'title':item[2],
            'actor':item[3].strip()[3:],
            'time':item[4].strip()[5:],
            'score':item[5]+item[6]
            }
    #猫眼TOP100所有信息写入文件
    def write_to_file(content):
        #encoding ='utf-8',ensure_ascii =False,使写入文件的代码显示为中文
        with open('result.txt','a',encoding ='utf-8') as f:
            f.write(json.dumps(content,ensure_ascii =False)+'\n')
            f.close()
    #下载电影封面
    def save_image_file(url,path):
      
        jd = requests.get(url)
        if jd.status_code == 200:
            with open(path,'wb') as f:
                f.write(jd.content)
                f.close()
      
    def main(offset):
        url = "https://maoyan.com/board/4?offset="+str(offset)
        html = get_one_page(url,headers)
        if not os.path.exists('covers'):
            os.mkdir('covers')  
        for item in parse_one_page(html):
            print(item)
            write_to_file(item)
            save_image_file(item['image'],'covers/'+item['title']+'.jpg')
      
    if __name__ == '__main__':
        #对每一页信息进行爬取
        for i in range(10):
            main(i*10)
    

    爬取结果如下:在这里插入图片描述
    在这里插入图片描述
    5.多线程抓取
    进行比较,发现多线程爬取时间明显较快:在这里插入图片描述
    多线程:
    在这里插入图片描述
    以下为完整代码:

    #-*-coding:utf-8-*-
    import requests
    import re
    import json
    import os
    from requests.exceptions import RequestException
    from multiprocessing import Pool
    #猫眼电影网站有反爬虫措施,设置headers后可以爬取
    headers = {
        'Content-Type': 'text/plain; charset=UTF-8',
        'Origin':'https://maoyan.com',
        'Referer':'https://maoyan.com/board/4',
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
        }
      
    #爬取网页源代码
    def get_one_page(url,headers):
        try:
            response =requests.get(url,headers =headers)
            if response.status_code == 200:
                return response.text
            return None
        except RequestsException:
            return None
      
    #正则表达式提取信息
    def parse_one_page(html):
        pattern = re.compile('<dd>.*?board-index.*?>(\d+)</i>.*?data-src="(.*?)".*?name"><a'
            +'.*?>(.*?)</a>.*?star">(.*?)</p>.*?releasetime">(.*?)</p>.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>',re.S)
        items = re.findall(pattern,html)
        for item in items:
            yield{
            'index':item[0],
            'image':item[1],
            'title':item[2],
            'actor':item[3].strip()[3:],
            'time':item[4].strip()[5:],
            'score':item[5]+item[6]
            }
    #猫眼TOP100所有信息写入文件
    def write_to_file(content):
        #encoding ='utf-8',ensure_ascii =False,使写入文件的代码显示为中文
        with open('result.txt','a',encoding ='utf-8') as f:
            f.write(json.dumps(content,ensure_ascii =False)+'\n')
            f.close()
    #下载电影封面
    def save_image_file(url,path):
      
        jd = requests.get(url)
        if jd.status_code == 200:
            with open(path,'wb') as f:
                f.write(jd.content)
                f.close()
      
    def main(offset):
        url = "https://maoyan.com/board/4?offset="+str(offset)
        html = get_one_page(url,headers)
        if not os.path.exists('covers'):
            os.mkdir('covers')  
        for item in parse_one_page(html):
            print(item)
            write_to_file(item)
            save_image_file(item['image'],'covers/'+item['title']+'.jpg')
      
    if __name__ == '__main__':
        #对每一页信息进行爬取
        pool = Pool()
        pool.map(main,[i*10 for i in range(10)])
        pool.close()
        pool.join()
    

    最后给大家推荐我们的Python学习扣qun:913066266 ,看看前辈们是如何学习的!从基础的python脚本到web开发、爬虫、django、数据挖掘等【PDF,实战源码】,零基础到项目实战的资料都有整理。送给每一位python的小伙伴!每天都有大牛定时讲解Python技术,分享一些学习的方法和需要注意的小细节,点击加入我们的 python学习者聚集地
    本文主要讲解了使用Python爬虫库requests多线程抓取猫眼电影TOP100数据的实例

    展开全文
  • Python多线程

    千次阅读 2019-04-27 21:59:57
    threading模块是Python支持多线程的重要模块,该模块是咋低层模块_thread的基础上开发的更高层次的线程编程接口,提供了大量的方法和类来支持多线程编程,极大地方便了用户。 threading模块常用方法: threading....
  • Python多线程编程

    千次阅读 2010-11-13 16:56:00
    Python多线程编程
  • Python多线程入门指南

    万次阅读 多人点赞 2017-02-25 21:48:54
    一直懒得写Python相关的文章,恰好有天需要简单的给童鞋们讲点课,仓促之余就诞生了此文.今天本来准备全面的聊聊有关高性能并发这个话题来着,但是周末马上要来了啊.所以我就取了其中的一点来介绍,关于其他的方面,有...
  • python多线程编程

    千次阅读 2016-05-22 13:27:18
    1、多线程的发展背景 随着计算机的发展,无论是硬件还是软件都在快速的发展。 在最开始的时候,计算机都是只有一个cpu来进行指令控制和运算,程序执行的时候都是一个进程一个进程的运行,也就是顺序执行的方式,...
  • Python多线程介绍

    2015-08-07 15:08:34
    Python开发过程中,有时候会用到多线程技术,例如在开发服务端程序时就不可避免的要使用多线程。 thread是相对低级的线程模块,threading是经过封装的功能比较强。 最简单的一个多线程例子 [codesyntax ...
  • 今天小编就为大家分享一篇浅谈PyQt5中异步刷新UI和Python多线程总结,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧 目前任务需要做一个界面程序,PyQt是非常方便的选择,QT丰富的控件以及python...
  • python爬取英雄联盟皮肤结合多线程的方法

    千次阅读 多人点赞 2021-05-26 15:40:33
    python爬取英雄联盟皮肤结合多线程的方法 1.什么是多线程多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。线程是在同一时间需要完成多项任务的时候实现的。 2....
  • python 多线程编程

    千次阅读 2009-03-02 16:59:00
    转自:http://hi.baidu.com/seekvista/blog/item/d626f8172cf0b608c83d6d14.html我们在做软件开发的时候很多要用到多线程技术。例如如果做一个下载软件象flashget就要用到、象在线视频工具realplayer也要用到因为要...
  • python多线程与多进程

    千次阅读 2018-03-06 10:57:35
    原文地址:...我在使用python的过程中,用到过几次多线程和多进程加速,觉得 充分利用CPU节省时间是一种很有“延长生命”的感觉。现将网络上看到的python多线程和多...
  • 这篇文章主要介绍了Python实现多线程的两种方式,结合实例形式分析了通过自定义函数传递Thread对象以及继承Thread类两种多线程实现方式相关操作技巧,需要的朋友可以参考下 本文实例讲述了Python实现多线程的两种方式...
  • △点击上方“Python猫”关注 ,回复“1”领取电子书剧照:凡人修仙传作者:后端技术指南针来源:后端技术指南针1.全局解释锁如题: Python多线程为什么不能利用多核处理器?全局...
  • Python爬虫4.1 — threading(多线程)用法教程

    千次阅读 多人点赞 2019-10-12 10:52:11
    Python爬虫4.1 — threading[多线程]用法教程 综述 本系列文档用于对Python爬虫技术的学习进行简单的教程讲解,巩固自己技术知识的同时,万一一不小心又正好对你有用那就更好了。 Python 版本是3.7.4 前面的文章记录...
  • Python 进阶(一):多线程

    千次阅读 2020-02-07 08:36:23
    一文熟悉多线程
  • Python的多线程在io方面比单线程还是有优势,但是在多线程开发时,少不了对文件的读写操作。在管理多个线程对同一文件的读写操作时,就少不了文件锁了。 使用fcntl 在linux下,python的标准库有现成的文件锁,来自于...
  • 只好学习控制线程数了,官方文档不好看,觉得结构不够清晰,网上找很多文章也都不很清晰,只有for全开线程,没有控制线程数的具体说明,最终终于根据多篇文章和官方文档算是搞明白基础的多线程怎么实现法了,怕长...
  • 这篇文章主要介绍了Python 多线程,threading模块,创建子线程的两种方式,结合实例形式分析了Python线程的原理与创建子线程的相关实现技巧,需要的朋友可以参考下 本文实例讲述了Python 多线程,threading模块,创建...
  • 大话多进程多线程 “进程——资源分配的最小单位,线程——程序执行的最小单位” 进程: 是程序执行时的一个实例,即它是程序已经执行到课中程度的数据结构的汇集。从内核的观点看,进程的目的就是担当分配系统...
  • 3.[Python]多线程程序Ctrl+C的优雅终止

    万次阅读 2015-08-15 08:19:30
    python多线程,Ctrl+C终止
  • 多线程编程中如果使用Condition对象代替lock, 能够实现在某个事件触发后才处理数据, condition中含有的方法: wait:线程挂起,收到notify通知后继续运行 notify:通知其他线程, 解除其它线程的wai状态 notifyAll(): ...
  • python安全开发/高级渗透技术

    千人学习 2019-04-15 11:32:00
    课程内容包括python语言应用、介绍、开发实例、python gui编程、网络编程、数据库编程、爬虫编程、python开发扫描器、爆炸工具、漏洞利用等,通过本教材的学习,我们可以深入了解最先进的python语言。在语言开发、...
  • 这三天时间基本上花在多线程上,但是效果好像还是不是特别好,怎么说呢,学校的老师再讲操作系统的时候我基本在睡觉,现在做这个确实比较难了。 学编程最重要的是心法,所以今天的开篇先说一点这半个月来的感受吧:...
  • 最近学了两个python库,一个负责管理线程,一个负责管理进程,原来一直写的都 是些单线程的程序,虽然web也关于...python对线程的支持并不是非常好,所以你可以在很多文章上批评python多线程的弊端,但是为什么p...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 62,340
精华内容 24,936
关键字:

多线程开发技术python

python 订阅