V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
pathetique
V2EX  ›  Python

弱极了,请教 Python 多线程如何快速开始

  •  
  •   pathetique · 2023-06-02 01:39:15 +08:00 · 4272 次点击
    这是一个创建于 540 天前的主题,其中的信息可能已经有所发展或是发生改变。
    文科轻拍

    晚上用 python 写雍正实录的词汇统计

    雍正的起居官特别能写,一百三十多万字,用 M1 pro 的电脑,半个小时才检索到三万多字(算法应该没问题),看了看 cpu ,只有一个 100%的占用

    有没有特别简单的 python 多线程调用入门,希望自己能理解的那种?

    不知道问清楚没有
    53 条回复    2023-06-03 23:21:44 +08:00
    noneusername
        1
    noneusername  
       2023-06-02 01:40:46 +08:00
    边看文档,边让 GPT 输出示例测试
    ysc3839
        2
    ysc3839  
       2023-06-02 02:03:57 +08:00 via Android   ❤️ 1
    CPU 密集运算用 Python 的多线程是没用的,需要多进程
    这种情况手动分段然后开多几个同时跑就好了
    Weixiao0725
        3
    Weixiao0725  
       2023-06-02 02:15:59 +08:00   ❤️ 1
    看一下 multiprocessing.Pool
    suith27
        4
    suith27  
       2023-06-02 02:19:25 +08:00 via iPhone
    shalingye
        5
    shalingye  
       2023-06-02 03:28:45 +08:00 via Android
    你需要的是多进程,简单得很,这是我的模板:
    if __name__=='__main__':
    from multiprocessing import Pool # 导入进程池
    path=choosefolder()
    p = Pool() # 创建进程池,留空代表 Cpu 的逻辑内核数目
    while True:
    try:
    for n in range(1,1000):
    p.apply_async(download, args=(n,path)) # 向进程池中添加任务
    except:
    p.close() # 结束向进程池中添加任务(后续不能再使用 apply_async 方法添加新任务)
    p.join() # 实现进程同步
    break
    shalingye
        6
    shalingye  
       2023-06-02 03:33:46 +08:00 via Android
    缩进寄了,自己排吧,choosefolder 和 download 都是函数名,args 代表 download 函数的参数,如果只有一个参数需要写成 n,的形式,把你需要分割处理的函数替换到 download 的位置
    fyq
        7
    fyq  
       2023-06-02 06:22:27 +08:00
    如果只有一个文档的话,最简单的方法是手动拆分成好几个,然后同时针对拆分后不同的的文档运行你的程序去分析,时候再针对得到的结果汇总一下就好了。
    liyafe1997
        8
    liyafe1997  
       2023-06-02 06:38:04 +08:00   ❤️ 2
    Python 的多线程是假的,忘了这东西吧,要干正事得看多进程 multiprocessing.Pool
    lovelylain
        9
    lovelylain  
       2023-06-02 08:28:04 +08:00 via Android
    python 有个著名的 GIL 锁,执行 Python 代码时都需要先获取这个锁,所以虽然多线程,但同一时间只有一个线程获得这个锁,其他线程都在等待,结果只能跑满一个核。如果你是 c 模块处理,可以手动释放锁处理完再获取,这样多线程就能突破单核限制。但 c 模块开发效率和可移植性很差,更好的方案是上面提到的 multiprocessing 多进程。
    tulongtou
        10
    tulongtou  
       2023-06-02 08:29:29 +08:00
    卧槽,现在文科生都这么优秀了么,卷到理科生这边来了
    litguy
        11
    litguy  
       2023-06-02 08:33:23 +08:00
    多进程,每个进程处理其中的一部分东西,最后所有进程结果汇总
    strawberrydafu
        12
    strawberrydafu  
       2023-06-02 08:34:04 +08:00
    检索具体是在干什么?半小时才 3 万字我怀疑算法本身有很多优化空间
    Kinnice
        13
    Kinnice  
       2023-06-02 08:39:30 +08:00 via Android   ❤️ 2
    词汇统计,m1 ,半小时 3w 字,看起来算法不像是没有问题
    ohayoo
        14
    ohayoo  
       2023-06-02 08:40:27 +08:00
    其实可以用第三方的任务队列库,自带多进程多线程选项,只需要关注自己的逻辑函数即可,都可以尝试对比下
    adoni
        15
    adoni  
       2023-06-02 11:15:57 +08:00
    参见: https://adoni.github.io/2019/01/07/python-practice/#concurrent-and-multi-process

    ```python
    from concurrent.futures import ProcessPoolExecutor
    with ProcessPoolExecutor(max_workers=16) as exe:
    result = exe.map(func, data_list)
    ```

    另外,你可以直接改成单线程,看一下,我感觉 130 万字,分分钟跑完。
    aijam
        16
    aijam  
       2023-06-02 11:54:43 +08:00
    "半个小时才检索到三万多字",这得多慢啊,口算可能都比这个快
    coderluan
        17
    coderluan  
       2023-06-02 12:27:34 +08:00
    这个你问 chatgpt 更方便,最简单应该是 concurrent.futures.ThreadPoolExecutor ,然后用 subprocess 调用你的程序开多进程。
    killva4624
        18
    killva4624  
       2023-06-02 12:30:22 +08:00
    多进程记得考虑进程安全
    laqow
        19
    laqow  
       2023-06-02 12:46:52 +08:00
    会不会算法其实有问题?至少 python 原生的文件 IO 和字符串处理慢的一匹,直接把所有文字 read 进内存再处理,或者以二进制读取后在 byte 对象上搜索都能快出个十倍来
    iX8NEGGn
        20
    iX8NEGGn  
       2023-06-02 14:13:32 +08:00 via iPhone
    半小时三万多字,算法肯定有问题。百万字算法合适的话也,词频统计也就几十秒
    lyz1990
        21
    lyz1990  
       2023-06-02 14:26:19 +08:00
    半小时 3 万慢得不太正常
    pathetique
        22
    pathetique  
    OP
       2023-06-02 14:49:37 +08:00
    @Kinnice @Weixiao0725 @adoni @aijam @coderluan @fyq @killva4624 @laqow @litguy @liyafe1997

    非常感谢各位的帮助!就不一一回复,几位大佬我一起感谢先

    先非常感谢对多线程和进程的区分,我已经开始用 multiprocessing module 了,在我的 M1max 上基本上十个小时可以做完,对于雍正可以接受了。但是随着搜索对象正则式的复杂化我怀疑还要更久更久,而且雍正才干几年,乾隆的起居录估计有十倍大,可能个人电脑还是不够。

    然后几位大佬怀疑算法有没有问题,我也不知道哪里可以改善的。我把最简单的版本(算二字成词率)代码放在这里,辣大家的眼可能,但是真心请问如果有耐心看完的大佬:有没有改进的空间?如果是单线程的话,我可以从头到尾扫描,扫过的不再碰,复杂度是 n^2/2 ,但是因为多线程,把文本分成多块就没头没尾的,复杂度是 n^2 但是可以多核平均。


    checkdict = [] #已经查过的单词词库
    list = [] #文本本身
    step = 100000 #每个机器人负责多大块

    with open('yong3.txt', 'r') as file:
    contents = file.read()
    for chr in contents:
    if chr != '\n' and chr != ' ' and chr != '。' and chr != '、' and chr != '○' and chr != '\u3000': #有些奇怪的字符去掉
    list.append(chr)
    length = len(list) #雍正大概 100 万字


    import multiprocessing.pool #import the multiprocessing module

    def worker(num):
    """Worker function for the process"""
    print(f'Worker {num} starting')

    count = 0
    for i in range(0+step*num,min(step+step*num,length-2)): #每个机器人负责 #step 长度的文字
    if (list[i:i+2] not in checkdict): #如果不在字典里
    count = 0
    for j in range(0,length-2): #全本比对,因为分块似乎只能这么做,不能从 i 开始?
    if list[i:i+2] == list[j:j+2]:
    count = count + 1
    checkdict.append(list[i:i+2]) #check 完,添进已查字典
    if count > 350: #出现次数高于 350 次的字显示出来
    print (list[i:i+2])
    print(i, count, f"{num}th worker at percentage = {format((i/step-num)*100, '.2f')}%" )
    print(f'Worker {num} finished')


    还是挺好玩的,发现很多有趣的事实,比如弄死了年羹尧雍正还常提,比如他喜欢的十三弟其实没那么常提。我其实拿清史资料是因为手头方便,下一步更想用 Colibri Core 处理黏着语(黏着语,比中文不一样的地方在于一个词根会有很多变化,比如 love, loves, loving )。

    想问调用 Calobri Core 这样的库啊包的各位大佬还有什么建议呢?还是照着 ChatGPT 的做?(我的 Python 真的很生,刚学,小时候会用 c )

    然后想问下,如果有人有时间帮小的看了这个案例,这种算法用显卡( CUDA )来帮助做会有帮助吗?或者我下一步弄大了,比如一千万字的更加模糊的搜索(比如犹太注经动辄几千万),有什么好的建议提速呢?可以借学校的服务器或者云吗?但是感觉云的 CPU 频率应该也一般。

    非常感谢大家指路!小的刚刚用电脑编程对付这些,求拍求建议求更多工具。
    Kinnice
        23
    Kinnice  
       2023-06-02 14:55:23 +08:00
    @pathetique #22 首先是做中文分词 eg: https://github.com/fxsjy/jieba ,然后应该可以非常快,提供个 datasource ?。
    julyclyde
        24
    julyclyde  
       2023-06-02 14:59:53 +08:00
    分词这事有前后顺序依赖吧,能多线程??
    Anarchy
        25
    Anarchy  
       2023-06-02 15:58:29 +08:00
    @julyclyde 按章节、段落、句子都可以啊,没人一句话写一本书的吧。
    zhzy
        26
    zhzy  
       2023-06-02 18:13:53 +08:00
    看了一下,没做分词,直接每两个字作为一个词;
    用 list 循环判断是否存在,而且文本里没有重复的文字么,用两个 for 循环感觉没什么必要,相当于每个词都翻一遍全文,O(n^2),不慢才怪吧。其实直接一个循环然后字典里+1 就行了,虽然也怪怪的,但是至少比现在的快;
    其实还是建议不要自己写,或者至少了解一下这类算法应该怎么写...完全用自己的思路闷头搞有时候真的不容易意识到代码里的问题...要不还是老老实实上 NLP 库
    ispinfx
        27
    ispinfx  
       2023-06-02 18:28:05 +08:00
    这么点体量多进程要跑 10 小时?
    zhzy
        28
    zhzy  
       2023-06-02 18:29:14 +08:00
    @zhzy Python 不是 c ,很多东西不需要自己实现的,而且尽量不要用内置的类型作为变量名,快速糊了一个版本,应该会快一些。
    zhzy
        29
    zhzy  
       2023-06-02 18:32:08 +08:00
    @zhzy #28 惊了,手快了,counter 那个循环里要判断一下有没有 key ,没有的话设置为 1 ,有的话加 1
    hellojukay
        30
    hellojukay  
       2023-06-02 18:34:53 +08:00
    1. list 如果长度非常的长,可能话很多时间在增长扩容上,建议使用 linklist
    2. if xx in [] , 这种判断方式效率非常低,应该使用 hash 的方式
    pathetique
        31
    pathetique  
    OP
       2023-06-02 18:38:07 +08:00
    @zhzy 谢过,我去试试用 panda !不用中文的分词 library 是因为用中文只是试试,其实主业是古代西方语言,最后需要 n-gram skipgram flexgram 之类的还是要自己微调很多自己写然后穷尽搜索。请问 regex 或者 panda 有好的入门建议吗?还,就 GPT 就好?
    pathetique
        32
    pathetique  
    OP
       2023-06-02 18:39:32 +08:00
    @hellojukay 感恩,我马上学一下 linklist 和 hash……
    zhzy
        33
    zhzy  
       2023-06-02 18:40:57 +08:00
    @pathetique #31 如果只是处理一下停用词的话就一两行代码,不需要学,抄一下就行,百万字其实很少的,只要避免循环嵌套这种性能问题不大的
    zhzy
        34
    zhzy  
       2023-06-02 18:48:03 +08:00
    @zhzy #29 还是把这个补上吧,用 get 的默认值,不用 if in keys 判断
    ispinfx
        35
    ispinfx  
       2023-06-02 18:53:41 +08:00
    不知道你是不是要 28 楼这种效果,100 万字跑了也就 0.几秒。
    ispinfx
        36
    ispinfx  
       2023-06-02 18:54:10 +08:00
    @zhzy #34 直接 collections.Counter()就能直接加了
    liyanm169gd
        37
    liyanm169gd  
       2023-06-02 19:02:31 +08:00
    猜一下 OP 的意思,如果是每两个字取下来然后统计频率可以这样做,假定你已经把文本读到了变量 tmpstr 里,total = [tmpstr[i]+tmpstr[i+1] for i in range(0,len(a)-1,2)],如果要统计可以直接 set ,然后拿 set 的值当 dict 的 key ,value 是 total.count("这里是 key")
    zhzy
        38
    zhzy  
       2023-06-02 19:25:54 +08:00
    @ispinfx #36 是的,可以直接根据文本构造一个步长为 1 每个元素长度为 2 的 list ,然后 Counter 就行,不过那样逻辑就被隐藏掉了,OP 的代码主要问题是扫描 list 来计数,这里只是展示一下更合理的逻辑
    ispinfx
        39
    ispinfx  
       2023-06-02 19:26:54 +08:00
    @zhzy #38 我的意思是用 Counter 你就不用 get 一下再加了。
    pathetique
        40
    pathetique  
    OP
       2023-06-02 20:32:57 +08:00
    @zhzy 跑通了,非常感谢!但是您的代码我还在慢慢理解中……因为不太理解 counter.get 或者 counter.items 这样的命令。是需要去 pathlib 的档案找意思吗?
    pathetique
        41
    pathetique  
    OP
       2023-06-02 20:37:10 +08:00
    @zhzy
    @ispinfx
    非常感谢
    pathetique
        42
    pathetique  
    OP
       2023-06-02 20:37:52 +08:00
    再问一个白痴问题,我和您方案的最大差别是不是创建的对象是不是 hashable 的差别?大概就是 30 楼大哥说 list 的效率低,hash 效率高的问题?
    非常非常感谢两位的时间!知道大家都挺忙……
    pathetique
        43
    pathetique  
    OP
       2023-06-02 20:38:31 +08:00
    奇怪为啥有些帖子回复发不出去说要注册 1001 天……1001nights 吗……
    zhzy
        44
    zhzy  
       2023-06-02 20:39:04 +08:00
    @pathetique #40 pathlib 是一个文件系统路径库,封装了一些比较方便的功能,比如可以直接读取文本,用来代替 with...as f: f.read ;
    counter 是一个字典,对于一个字典对象 d ,d.items 是取出每一项的键(key)和值(value)
    for k, v in d.items()这个循环相当于逐一取出字典的每一项
    counter.get(key, defaultValue)实际上可以理解为 if key in counter.keys: counter[key]; else: return defaultValue, 作用是尝试根据键读取字典的一个值,如果不存在这样的键, 返回 defaultValue
    在 python 里其实这类方法很多, 可以节省很多代码量, 并且速度要比你自己写的逻辑快, 因为在底层它可能会做一些优化
    zhzy
        45
    zhzy  
       2023-06-02 20:40:56 +08:00
    @ispinfx #39 啊 我明白您的意思了, 其实我的意思是 collections.Counter 是可以直接传一个可迭代对象或者 map 的, 比如传一个 str 或 list 进去就可以直接做计算
    aitianci
        46
    aitianci  
       2023-06-02 20:55:38 +08:00
    @Kinnice #23 我玩了一下,用 jieba 出来的效果还不如 re 。我是从网上下载的一个 2MB 多的雍正实录,到 115 卷就没了。jieba 给出的全是单字词,像“朕,等,为,有”这种,re 能给“出谕内阁,应如所请,寻议,下部知之,入祠致祭如例,均应如所请,缘事革职,谕大学士等,给银建坊”这种还有点含义的词组。
    LaurelHarmon
        47
    LaurelHarmon  
       2023-06-02 20:57:43 +08:00   ❤️ 1
    笑死,这点数据量,跟多线程、多进程没半毛钱关系,你还是先理清楚需求,然后改一下代码。正常情况秒秒钟出来,暴力循环不可取。
    pathetique
        48
    pathetique  
    OP
       2023-06-02 21:12:16 +08:00
    @aitianci 嗯嗯,其实最大的价值、不是在于观察已有的成形的 pattern 或者单词,而恰恰是一些直观不熟悉但统计上又显著的词汇、语法结构或者说法。比如很多语言中动词-主语-宾语顺序多,但某些地方开始突然高密度调用其他语序;或者很多语言的果-因表达多,但如果突然用因-果连词;或者突然用佶屈聱牙的、甚至字典上没有的词,就很可能有很重要的、非母语者不容易看出的情绪或者信息。反常的信息最珍贵
    pathetique
        49
    pathetique  
    OP
       2023-06-02 21:12:43 +08:00
    @zhzy 非常感谢,我去读下 pathlib 的文档~
    Deplay
        50
    Deplay  
       2023-06-03 00:24:05 +08:00
    单从实现这个任务来讲,使用现成的 wheel 更好的选择,自己写还是很难的,例如#23 所提及的 jieba
    也可以考虑上 nlp ,nltk 应该就可以(这一块没怎么接触过,不是很清楚)
    至于你提到用 gpu 处理,理论上确实可以,但没必要,毕竟不是很复杂的任务,dl 没必要,学习成本过高
    julyclyde
        51
    julyclyde  
       2023-06-03 12:03:59 +08:00
    @Anarchy 分章节、分段落是人工还是自动呢?如果是自动,那也得按顺序过一遍程序啊,这环节也是不能并发的吧
    phoulx
        52
    phoulx  
       2023-06-03 17:52:42 +08:00
    #48 感觉 up 做的东西很有意思呢,之前专业相关,Python 也懂一点。看上去主楼问题已经解决了?如果仍有疑问,欢迎联系 https://paste.rs/Zb0gs ,也许可以提供一点帮助!
    klion
        53
    klion  
       2023-06-03 23:21:44 +08:00
    就算文字有 1000 万,也不过不到 100M 的内存,这怎么会 3 万字跑半个小时啊,是不是你自己用了太多 for 做遍历了,看起来更像是你的代码需要优化,你去用 profile 逐行分析代码性能瓶颈再讨论吧
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2774 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 10:07 · PVG 18:07 · LAX 02:07 · JFK 05:07
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.