关于对python多线程处理数据的理解(以进程为例相对比)

多线程处理能够合理地分配内存,使得计算速度大大提高,经过楼主的测试计算的时间能够缩短百分之五十!!

定义:

线程是指进程内的一个执行单元,也是进程内的可调度实体.

与进程的区别:
(1) 地址空间:进程内的一个执行单元;进程至少有一个线程;它们共享进程的地址空间;而进程有自己独立的地址空间;
(2) 资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
(3) 线程是处理器调度的基本单位,但进程不是.
(4) 二者均可并发执行.

简而言之,一个程序至少有一个进程,一个进程至少有一个线程.

线程的划分尺度小于进程,使得多线程程序的并发性高。
另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。


直白地描述:可以认为程序是一个国家,而进程代表一个省,线程则代表一个地级市。一个小国家可以只有一个省,一个小的省也可以只有一个地级市。

进程就是一个应用程序在处理机上的一次执行过程,它是一个动态的概念,而线程是进程中的一部分,进程包含多个线程在运行。

​ 多线程可以共享全局变量,多进程不能。多线程中,所有子线程的进程号相同;多进程中,不同的子进程进程号不同。

​ 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
​ 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
​ 一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.

线程是程序处理的最基本单位,python中已经有了threading模块用于线程处理

python主要是通过thread和threading这两个模块来实现多线程支持。python的thread模块是比较底层的模块,python的threading模块是对thread做了一些封装,可以更加方便的被使用。但是python(cpython)由于GIL的存在无法使用threading充分利用CPU资源,如果想充分发挥多核CPU的计算能力需要使用multiprocessing模块(Windows下使用会有诸多问题)。

线程池的优势

传统多线程方案会使用“即时创建, 即时销毁”的策略。尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数极其频繁,那么服务器将处于不停的创建线程,销毁线程的状态。

​ 一个线程的运行时间可以分为3部分:线程的启动时间、线程体的运行时间和线程的销毁时间。在多线程处理的情景中,如果线程不能被重用,就意味着每次创建都需要经过启动、销毁和运行3个过程。这必然会增加系统相应的时间,降低了效率。

有没有一种高效的解决方案呢? —— 线程池

线程池基本原理:

​ 我们把任务放进队列中去,然后开N个线程,每个线程都去队列中取一个任务,执行完了之后告诉系统说我执行完了,然后接着去队列中取下一个任务,直至队列中所有任务取空,退出线程。

使用线程池:
​ 由于线程预先被创建并放入线程池中,同时处理完当前任务之后并不销毁而是被安排处理下一个任务,因此能够避免多次创建线程,从而节省线程创建和销毁的开销,能带来更好的性能和系统稳定性。

线程池的设置参数问题

服务器CPU核数有限,能够同时并发的线程数有限,并不是开得越多越好,以及线程切换是有开销的,如果线程切换过于频繁,反而会使性能降低

线程执行过程中,计算时间分为两部分:

CPU计算,占用CPU
不需要CPU计算,不占用CPU,等待IO返回,比如recv(), accept(), sleep()等操作,具体操作就是比如
访问cache、RPC调用下游service、访问DB,等需要网络调用的操作
那么如果计算时间占50%, 等待时间50%,那么为了利用率达到最高,可以开2个线程:
假如工作时间是2秒, CPU计算完1秒后,线程等待IO的时候需要1秒,此时CPU空闲了,这时就可以切换到另外一个线程,让CPU工作1秒后,线程等待IO需要1秒,此时CPU又可以切回去,第一个线程这时刚好完成了1秒的IO等待,可以让CPU继续工作,就这样循环的在两个线程之前切换操作。

那么如果计算时间占20%, 等待时间80%,那么为了利用率达到最高,可以开5个线程:
可以想象成完成任务需要5秒,CPU占用1秒,等待时间4秒,CPU在线程等待时,可以同时再激活4个线程,这样就把CPU和IO等待时间,最大化的重叠起来

抽象一下,计算线程数设置的公式就是:
N核服务器,通过执行业务的单线程分析出本地计算时间为x,等待时间为y,则工作线程数(线程池线程数)设置为 N*(x+y)/x,能让CPU的利用率最大化。
由于有GIL的影响,python只能使用到1个核,所以这里设置N=1

以下是实现进程池的多进程代码

import queue
import threading
import time

# 声明线程池管理类
class WorkManager(object):
   def __init__(self, work_num=1000, thread_num=2):
      self.work_queue = queue.Queue()  # 任务队列
      self.threads = []  # 线程池
      self.__init_work_queue(work_num)  # 初始化任务队列,添加任务
      self.__init_thread_pool(thread_num) # 初始化线程池,创建线程

   """
      初始化线程池
   """
   def __init_thread_pool(self, thread_num):
      for i in range(thread_num):
         # 创建工作线程(线程池中的对象)
         self.threads.append(Work(self.work_queue))


   """
      初始化工作队列
   """
   def __init_work_queue(self, jobs_num):
      for i in range(jobs_num):
         self.add_job(do_job, i)

   """
      添加一项工作入队
   """
   def add_job(self, func, *args):
      self.work_queue.put((func, list(args)))  # 任务入队,Queue内部实现了同步机制

   """
      等待所有线程运行完毕
   """
   def wait_allcomplete(self):
      for item in self.threads:
         if item.isAlive(): item.join()


class Work(threading.Thread):
   def __init__(self, work_queue):
      threading.Thread.__init__(self)
      self.work_queue = work_queue
      self.start()

   def run(self):
      # 死循环,从而让创建的线程在一定条件下关闭退出
      while True:
         try:
            do, args = self.work_queue.get(block=False)  # 任务异步出队,Queue内部实现了同步机制
            do(args)
            self.work_queue.task_done()  # 通知系统任务完成
         except:
            break

# 具体要做的任务
def do_job(args):
   time.sleep(0.1)  # 模拟处理时间
   print(threading.current_thread())
   print(list(args))


if __name__ == '__main__':
   start = time.time()
   work_manager = WorkManager(100, 10)  # 或者work_manager =  WorkManager(10000, 20)
   work_manager.wait_allcomplete()
   end = time.time()
   print("cost all time: %s" % (end - start))

线程间的同步:

如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。

​ 使用Thread对象的Lock和Rlock可以实现简单的线程同步,这两个对象都有acquire方法和release方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到acquire和release方法之间。

​ 需要注意的是,Python有一个GIL(Global Interpreter Lock)机制,任何线程在运行之前必须获取这个全局锁才能执行,每当执行完100条字节码,全局锁才会释放,切换到其他线程执行。

3.1 线程同步问题
多线程实现同步有四种方式:

锁机制,信号量,条件判断和同步队列。

下面我主要关注两种同步机制:锁机制和同步队列。


锁机制同步:

threading的Lock类,用该类的acquire函数进行加锁,用realease函数进行解锁

import threading
import time
class myThread(threading.Thread):
   def __init__(self, threadID, name, counter):
      threading.Thread.__init__(self)
      self.threadID = threadID
      self.name = name
      self.counter = counter
   def run(self):
      print("Starting " + self.name)
      # 获得锁,成功获得锁定后返回True
      # 可选的timeout参数不填时将一直阻塞直到获得锁定
      # 否则超时后将返回False
      threadLock.acquire()
      print_time(self.name, self.counter, 5)
      # 释放锁
      threadLock.realease()
def print_time(threadName, delay, counter):
   while counter:
      time.sleep(delay)
      print("%s: %s" % (threadName, time.ctime(time.time())))
      counter -= 1

threadLock = threading.Lock()
threads = []
# 创建新线程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)
# 开启新线程
thread1.start()
thread2.start()
# 添加线程到线程列表
threads.append(thread1)
threads.append(thread2)
# 等待所有线程完成
for t in threads:
   t.join()

print("Exiting Main Thread")

利用queue语句

python2.x中提供的Queue, Python3.x中提供的是queue

见import queue.

Python的queue模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列PriorityQueue。这些队列都实现了锁原语,能够在多线程中直接使用。可以使用队列来实现线程间的同步。

queue模块中的常用方法:

queue.qsize() 返回队列的大小
queue.empty() 如果队列为空,返回True,反之False
queue.full() 如果队列满了,返回True,反之False
queue.full 与 maxsize 大小对应
queue.get([block[, timeout]])获取队列,timeout等待时间
queue.get_nowait() 相当Queue.get(False)
queue.put(item) 写入队列,timeout等待时间
queue.put_nowait(item) 相当Queue.put(item, False)
queue.task_done() 在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列发送一个信号
queue.join() 实际上意味着等到队列为空,再执行别的操作

import queue
import threading
import time

exitFlag = 0

class myThread(threading.Thread):
   def __init__(self, threadID, name, q):
      threading.Thread.__init__(self)
      self.threadID = threadID
      self.name = name
      self.q = q

   def run(self):
      print("Starting " + self.name)
      process_data(self.name, self.q)
      print("Exiting " + self.name)

def process_data(threadName, q):
   while not exitFlag:
      queueLock.acquire()
      if not workQueue.empty():
         data = q.get()
         queueLock.release()
         print("%s processing %s" % (threadName, data))
      else:
         queueLock.release()
      time.sleep(1)

threadList = ["Thread-1", "Thread-2", "Thread-3"]
nameList = ["One", "Two", "Three", "Four", "Five"]
queueLock = threading.Lock()
workQueue = queue.Queue(10)
threads = []
threadID = 1

# 创建新线程
for tName in threadList:
   thread = myThread(threadID, tName, workQueue)
   thread.start()
   threads.append(thread)
   threadID += 1

# 填充队列
queueLock.acquire()
for word in nameList:
   workQueue.put(word)
queueLock.release()

# 等待队列清空
while not workQueue.empty():
   pass

# 通知线程是时候退出
exitFlag = 1

# 等待所有线程完成
for t in threads:
   t.join()
print("Exiting Main Thread")
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Python 的多线程(multithreading)和多进程(multiprocessing)模块可以用于处理数据,提高程序效率。下面是一个简单的子: 使用多线处理数据 ``` import threading def process_data(data): # 处理数据的函数 pass def main(): data = get_data() # 获取数据 threads = [] for i in range(4): # 创建4个线程 t = threading.Thread(target=process_data, args=(data,)) threads.append(t) t.start() # 等待所有线程完成 for t in threads: t.join() if __name__ == '__main__': main() ``` 上面的代码中,我们首先定义了一个处理数据的函数 process_data(),然后使用多线程模块创建了 4 个线程来处理数据。最后等待所有线程完成。 使用多进程处理数据 ``` import multiprocessing def process_data(data): # 处理数据的函数 pass def main(): data = get_data() # 获取数据 processes = [] for i in range(4): # 创建4个进程 p = multiprocessing.Process(target=process_data, args=(data,)) processes.append(p) p.start() # 等待所有进程完成 for p in processes: p.join() if __name__ == '__main__': main() ``` 上面的代码中,我们首先定义了一个处理数据的函数 process_data(),然后使用多进程模块创建了 4 个进程处理数据。最后等待所有进程完成。 需要注意的是,多进程处理数据的开销比多线程大,因为每个进程都需要独立的内存空间和上下文。因此,如果处理数据量较小,可以使用多线程模块,而如果处理数据量较大,可以考虑使用多进程模块。 ### 回答2: Python中的多线程是一种并发编程的方式,可以有效地处理数据。多线程可以同时执行多个任务,提高程序的运行效率和响应速度。 在Python中,可以使用内置的`threading`模块来实现多线程编程。通过创建多个线程对象,每个线程都可以独立执行不同的任务。这样可以在处理数据时同时执行多个任务,提高数据处理的速度。 多线处理数据时,需要考虑线程的同步和数据共享问题。由于多个线程会同时访问和修改共享的数据,可能会导致数据冲突和不一致。为了解决这个问题,可以使用锁(Lock)或其他同步机制来确保线程之间的数据同步和一致性。 在多线处理数据时,还需要注意线程的安全性。Python中的全局解释器锁(Global Interpreter Lock,GIL)限制了线程的并行执行,使得多线程不能真正实现并行处理。因此,对于某些密集计算型的任务,多线程可能并不能提升性能。 在结合Python的多线处理数据时,可以根据实际情况选择合适的线程数和线程策略,以达到最佳的数据处理效果。此外,还可以通过多进程、协程等方式来处理数据,根据具体需求选择合适的并发编程方式。 ### 回答3: Python是一种非常流行的编程语言,它有很多强大的库和工具,其中包括对多线处理数据的支持。多线程是一种并发编程的方式,它可以让程序同时执行多个任务,提高程序的效率。 在Python中,可以使用内置的`threading`模块来实现多线处理数据。这个模块提供了创建和管理线程的功能,可以让我们更方便地使用多线程。 要使用多线处理数据,首先我们需要定义一个线程函数,这个函数会被多个线程同时执行。在这个函数中,我们可以编写具体的数据处理逻辑。然后,我们可以使用`threading.Thread`类创建多个线程,并将线程函数作为参数传递给这些线程对象。最后,我们只需要调用这些线程对象的`start`方法,就可以启动这些线程并开始并行处理数据了。 多线处理数据的一个常见应用场景是并行计算。如,我们可以将一个大型的计算任务分成多个子任务,然后分配给不同的线程同时进行计算。这样,整个计算过程可以在较短的时间内完成。 另一个常见的应用场景是并行IO操作,比如数据的读取和写入。通过将这些IO操作分配给不同的线程,可以提高数据处理速度。 需要注意的是,在多线处理数据时,我们需要注意处理线程之间的同步和互斥问题。多个线程同时访问共享的数据时,可能会引发竞争条件和数据不一致的问题。为了避免这些问题,我们可以使用锁或者其他同步机制来保证线程之间的正确和有序的执行。 总的来说,Python中的多线程可以帮助我们实现并发处理数据的需求,提高程序的效率。不过,要合理使用多线程,并注意处理线程之间的同步和互斥问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值