Board logo

标题: [原创教程] 如何写多线程? [打印本页]

作者: xczxczxcz    时间: 2021-4-26 14:26     标题: 如何写多线程?

偶看了许多坛内坛外的高手的多线程代码,PY POWERSHELL C# C++的。 发现很多都有一个共同爱好,动不动就索数据。 lock(CustomDatas){};
偶想说的是:这他妈的哪是多线程。是打着多线程的名义做的却是同步命令。占着茅坑不拉屎纯耗资源。关键很多名教程书籍也动不动就锁资源,误人子弟

偶的做法:一定要先建立一个 自定义数据是类型不是集合,用来存储多线程下载内容。并且做每个数据独立,让它在存储在堆内存上(不要做死存在栈上),比如1万条数据,那么在堆内存中就有1万个坑,然后(异步)建立线程池。IO线程。让指定的线程数不停地去处理这一万个坑。【比如10个线程,首先处理前10个坑,若其中一个线程先处理完了,它立即去处理第11个坑,已处理完的数据就留在坑里,这样直到所有的坑处理完为止】。最后 Dispose() 数据类型。
测试 下载 网页小说,4~5千章内容,也就眨几下眼完成了。CPU使用率相当低。20个线程内存点用几十兆。机械硬盘读写数据时尽量不要多线程(偶测试2条线程内有点优势,40条到80时硬盘停转假死,磁头不知道该读哪个区),

如果觉得偶的做法不好或说有不清,可以喷轻点。
作者: yhcfsr    时间: 2021-4-26 16:57

大神来个实例,学习下
作者: went    时间: 2021-4-27 10:26

为了解决线程同步问题,锁定共享资源是必须的.但仅仅是访问该资源对象的时候只允许一个线程进入,这个过程往往时间很短甚至可以忽略.共享资源对象访问完毕后,线程执行任务时仍然是互不干扰的
对于多线程下载文件时,文件对象并不需要线程同步加锁,每个线程写入的数据段(开始位置和数据长度)都不同,本来就互不干扰,也就不存在线程同步问题
个人见解,希望能帮到你.
作者: 523066680    时间: 2021-4-27 11:24

本帖最后由 523066680 于 2021-4-27 14:09 编辑

多线程抓取网络数据的场景,耗时更多的仍然是网络部分。
即使是一个序列化数组(串行操作),若只是在push, shift操作的前后加锁和解锁(而不是在网络请求阶段就开始锁),占用的时间其实微不足道。

对于高性能并行运算的数据共享,不太了解。GPU Shader 的运算是可以并行处理最后直接输出值的,但是场景特殊。

比如10个线程,首先处理前10个坑,若其中一个线程先处理完了,它立即去处理第11个坑,已处理完的数据就留在坑里,这样直到所有的坑处理完为止

之前做过类似的做法,开10个常驻线程,他们从一个共享数组中获取任务并输出结果。也分为两种方式
1. 一开始就为线程分配等量的任务,不过有的线程会先处理完所有任务,提前进入空闲或者detach
2. 每个线程共享一个index索引,谁执行完了,就领取下一个任务。为了避免$index计数冲突, 依然要用到变量锁

后来改用队列
Thread::Queue 包为线程提供了线程安全的队列支持。与信号量类似,从内部实现上看,Thread::Queue 也是把一个通过锁机制实现同步访问的共享队列封装成了一个线程安全的包,并提供统一的使用接口。Thread::Queue 在某些情况下可以大大简化线程间通信的难度和成本。例如在生产者 - 消费者模型中,生产者可以不断地在线程队列上做 enqueue 操作,而消费者只需要不断地在线程队列上做 dequeue 操作,这就很简单地实现了生产者和消费者之间同步的问题。例如


再后来用上了 Mojolicious,省心
  1. # Non-blocking request
  2. $ua->get('mojolicious.org' => sub ($ua, $tx) { say $tx->result->dom->at('title')->text });
  3. Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
复制代码
  1. my $wdir = "D:/blah";
  2. for my $city ( keys %list )
  3. {
  4.     my $name = sprintf "%s/%s.json", $wdir, gbk($city);
  5.     $query->{"toCountryId"} = $ct_code->{$city};  # 城市ID 更新到请求数据中
  6.     $res = $ua->post( $url, to_json( $query ), closure->( $name ) );
  7. }
  8. $loop->start unless $loop->is_running;
  9. sub closure ($file) {
  10.     return sub ($ua, $tx) {
  11.         printf "%s\n", $file;
  12.         write_file( $file, $tx->result->body );
  13.     }
  14. }
复制代码

作者: xczxczxcz    时间: 2021-4-27 13:04

首先 感谢 大家的回复。
一、带锁的危险性
    在异步多线程时,线程量很大时,线程请求频繁时,锁会让你的异步多线程崩溃。几年前一直在测试这个问题,偶看过的大多数线程教程都有问题。只是他们的例子太简单,反映不出问题。理论和实际是有区别的。

二、真正的多线程是就应该不带锁的
    多线程就应该自由不受线程约束。 如10个和尚挑水把水缸装满。一次只一个人倒水,哪怕挑得再快,也和同步没多大区别。若把水缸换成10个水水缸,每个各尚负责一个缸,那立马搞定。
    带了锁的还是划分到队列或者链表等合理些。

三、偶这几年自行摸索的方法
    经过长时间测试,本地也好网络也好,发现运行良好,非常稳定,也许在其他教程上有,只是偶看得太少。
    方法:
   自定义类型封装:占21个字节
   1;所封装的数据在整个数据中的位置, 点4个字节;
   2;所封装的数据在内存所有坑中的索引或内存地址(C++)。  占4个字节;
   3;封装的数据线程完成状态,字符串型 占4个字节;线程执行完,说一声我做完了,
   4;封装的数据执行结果, bool 占1个字节,线程执行完把结果成功与否丟下就走了,
   5;封装数据的统一取消指令, 事件通知, 占4个字节 (类似于群主发一条信息,所有的群成员都马上知悉,并处理这一指令)
   6;封装数据接收结果的内存地址, 占4个字节,线程把处理完的数据的快递地址留下就不管了。
  
   经过这样处理包装每一个数据后,通过线程池进入异步下载,然后就不用管他了,只接收它的信号通知就可以了, 如果是客户端就信号显示在表格中。
   如果想取消下载,只要发个通知,所有线程马上停止(这个偶弄了好久,开始总是不能随意控制,技术太差,现在好多了,后台的异步也要让它停下来,线程池中也要让它停下来。)
作者: Gin_Q    时间: 2021-4-27 15:49

本帖最后由 Gin_Q 于 2021-4-27 20:52 编辑

售票系统就是一个使用锁的列子!

python 事件锁挺好用
  1. import threading
  2. import time
  3. def fun(event_lock, tips):
  4.     while True:
  5.         event_lock.wait()
  6.         print(tips)
  7.         time.sleep(1)
  8.         
  9. if __name__ == "__main__":
  10.     event_lock = threading.Event()
  11.     event_lock.set()
  12.     t1 = threading.Thread(target = fun, args = (event_lock, "线程 1"), daemon = True)
  13.     t2 = threading.Thread(target = fun, args = (event_lock, "线程 2"), daemon = True)
  14.    
  15.     t1.start()
  16.     t2.start()
  17.    
  18.     input_str = ''
  19.     while True:
  20.         input_str = input("1: 暂定线程 2: 开始线程")
  21.         if input_str == "1":
  22.             event_lock.clear()
  23.         elif input_str == "2":
  24.             event_lock.set()
  25.     t1.join()
复制代码

作者: 老刘1号    时间: 2021-4-28 10:48

最近在看这篇:https://blog.csdn.net/wenbingoon/article/details/9004512
作者: Gin_Q    时间: 2021-4-28 11:29

怎么说是队列呢? 他们是并行的!
作者: xczxczxcz    时间: 2021-4-28 11:33

回复 7# 老刘1号

打开看了下,哈不错,说实话,和我上面的方法很类似。纯异步无任何阻塞。用事件消息通知。根据我的经验,对于WEB:线程数最好大于I/O数,考虑服务器承压。对于本地,I/O要大于线程数,并发能力更大。大多书和微软文档都没看到有I/O的详细设置,纯靠摸索。累。
作者: Gin_Q    时间: 2021-4-28 11:35

回复 7# 老刘1号


    太硬核了!
作者: xczxczxcz    时间: 2021-4-28 11:37

回复 8# Gin_Q


    五一到了,大家开车在高速上奔跑,结果被收费站来个排队交钱。又是几条车龙?
作者: Gin_Q    时间: 2021-4-28 11:43

回复 11# xczxczxcz


   不下高速公路就可以啦!你要访问大家共同访问的资源(收费站),这个就需要锁!
作者: xczxczxcz    时间: 2021-4-28 11:51

new Thread 这几乎被微软抛弃了, 过时,占资源, POWERSHELL 用这种方式开个20条线程,其它的事都不用做了,异步开个20条线程,CPU还在那睡觉。
作者: Gin_Q    时间: 2021-4-28 14:20

回复 13# xczxczxcz


    线程本身是并行的,会占用cpu资源,当线程被锁住时,cpu资源将被释放。
C# 示例
  1. using System;
  2. using System.Threading;
  3. class Test
  4. {
  5.     static void Main()
  6.     {
  7.         int PoolSize = Environment.ProcessorCount; // 获取当前计算机上的处理器数。
  8.          
  9.         EventWaitHandle ewh = new EventWaitHandle(false, EventResetMode.ManualReset); // 事件锁
  10.         
  11.         Thread[] ThreadPool = new Thread[PoolSize];
  12.          
  13.         for (sbyte i=0; i<PoolSize; i++)
  14.         {
  15.             ThreadPool[i] = new Thread(Occupy);
  16.             ThreadPool[i].IsBackground = true;
  17.         }
  18.         for (sbyte i=0; i<PoolSize; i++)
  19.             ThreadPool[i].Start(ewh);
  20.          
  21.         while (true)
  22.         {
  23.             Console.Write("1 占用cpu资源\n2 释放cpu资源\n");
  24.             string input = Console.ReadLine();
  25.             if (input == "1")
  26.             {
  27.                 ewh.Set();
  28.             }
  29.             else if (input == "2")
  30.             {
  31.                 ewh.Reset();
  32.             }
  33.         }
  34.     }
  35.      
  36.     static void Occupy(object EventLock)
  37.     {
  38.         EventWaitHandle el = EventLock as EventWaitHandle;
  39.         while (true)
  40.         {
  41.             el.WaitOne();
  42.         }
  43.     }
  44. }
复制代码

作者: xczxczxcz    时间: 2021-5-8 17:30

回复 14# Gin_Q


    测试外链 https://v.youku.com/v_show/id_XNTE0OTg3OTExMg==.html
没在这用过外链,看行不行。
作者: Gin_Q    时间: 2021-5-8 18:23

回复 15# xczxczxcz


    速度很快,爬这么快不怕封ip吗?
作者: xczxczxcz    时间: 2021-5-8 18:49

回复 16# Gin_Q

偶这是完全按照 网站的请求方式 获取,所有的网页读取 gzip 数据流。回来再解码并判断编码,测试过80个线程,网站也不封,可能是因为异步的。
你上次那个 biqugeso.com 这个垃圾网,按常规下载,每个线程要间隔0.5秒左右,否则后面的线程很容易被拒绝请求,按新方法20个线程可迅速下载。

偶不爬小说,纯是为了测试线程的封闭方法哪种更好些。
作者: Gin_Q    时间: 2021-5-8 18:57

回复 17# xczxczxcz


    很厉害。网络交互我不懂!
作者: 523066680    时间: 2021-5-8 22:23

回复 18# Gin_Q


    平时习惯了看头像不看名字,结果这楼看下来都是二维码……
作者: Gin_Q    时间: 2021-5-8 22:58

回复 19# 523066680


    见笑了哈。。




欢迎光临 批处理之家 (http://bbs.bathome.net/) Powered by Discuz! 7.2