Python Multi-Processing多线程编程

本文介绍了Python中创建多线程的两种方法:使用thread模块的start_new_thread函数和使用threading模块创建Thread类实例。并通过示例展示了如何利用互斥锁解决多线程间的数据同步问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天总结一下Python的Multi-Processing多线程编程。

创建多进程的方法

start new thread

使用thread.start_new_thread是一种最简单的创建多进程的方法。

#!/usr/bin/python

import thread
import time

# Define a function for the thread
def print_time(threadName, delay):
   count = 0
   while count < 5:
      time.sleep(delay)
      count += 1
      print "%s: %s"%(threadName, time.ctime(time.time()))

# Create two threads as follows
try:
   thread.start_new_thread(print_time, ("Thread-1", 2,))
   thread.start_new_thread(print_time, ("Thread-2", 4,))
except:
   print "Error: unable to start thread"

while 1:
   pass

输出结果为:

Thread-1: Wed Sep 27 19:52:57 2017
Thread-2: Wed Sep 27 19:52:59 2017
Thread-1: Wed Sep 27 19:52:59 2017
Thread-1: Wed Sep 27 19:53:01 2017
Thread-1: Wed Sep 27 19:53:03 2017
Thread-2: Wed Sep 27 19:53:03 2017
Thread-1: Wed Sep 27 19:53:05 2017
Thread-2: Wed Sep 27 19:53:07 2017
Thread-2: Wed Sep 27 19:53:11 2017
Thread-2: Wed Sep 27 19:53:15 2017

如果没有程序末尾的while无限循环,thread1和thread2之外的主线程瞬间就会结束,程序就退出了,因此会什么也不打印。

threading module

#!/usr/bin/python

import threading
import time

exitFlag = 0

class myThread(threading.Thread):

   def __init__(self, threadID, name, delay):
      threading.Thread.__init__(self)
      self.threadID = threadID
      self.name = name
      self.delay = delay

   def run(self):
      print "Starting " + self.name
      print_time(self.name, 5, self.delay)
      print "Exiting " + self.name

def print_time(threadName, counter, delay):
   while counter:
      if exitFlag:
         threadName.exit()
      time.sleep(delay)
      print "%s: %s" % (threadName, time.ctime(time.time()))
      counter -= 1

# Create new threads
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

# Start new Threads
thread1.start()
thread2.start()

print "Exiting Main Thread"

输出结果为:

Starting Thread-1
Starting Thread-2
Exiting Main Thread
Thread-1: Wed Sep 27 20:00:16 2017
Thread-2: Wed Sep 27 20:00:17 2017
Thread-1: Wed Sep 27 20:00:17 2017
Thread-1: Wed Sep 27 20:00:18 2017
Thread-2: Wed Sep 27 20:00:19 2017
Thread-1: Wed Sep 27 20:00:19 2017
Thread-1: Wed Sep 27 20:00:20 2017
Exiting Thread-1
Thread-2: Wed Sep 27 20:00:21 2017
Thread-2: Wed Sep 27 20:00:23 2017
Thread-2: Wed Sep 27 20:00:25 2017
Exiting Thread-2

也可以通过加入线程列表的形式,等待线程执行结束后主线程才退出:

#!/usr/bin/python

import threading
import time

exitFlag = 0

class myThread(threading.Thread):

   def __init__(self, threadID, name, delay):
      threading.Thread.__init__(self)
      self.threadID = threadID
      self.name = name
      self.delay = delay

   def run(self):
      print "Starting " + self.name
      print_time(self.name, 5, self.delay)
      print "Exiting " + self.name

def print_time(threadName, counter, delay):
   while counter:
      if exitFlag:
         threadName.exit()
      time.sleep(delay)
      print "%s: %s" % (threadName, time.ctime(time.time()))
      counter -= 1

# Create new threads
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

# Start new Threads
thread1.start()
thread2.start()

# Add threads to thread list
threads = []
threads.append(thread1)
threads.append(thread2)

# Wait for all threads to complete
for t in threads:
    t.join()

print "Exiting Main Thread"

输出结果为:

Starting Thread-1
Starting Thread-2
Thread-1: Wed Sep 27 20:04:21 2017
Thread-1: Wed Sep 27 20:04:22 2017
Thread-2: Wed Sep 27 20:04:22 2017
Thread-1: Wed Sep 27 20:04:23 2017
Thread-2: Wed Sep 27 20:04:24 2017
Thread-1: Wed Sep 27 20:04:24 2017
Thread-1: Wed Sep 27 20:04:25 2017
Exiting Thread-1
Thread-2: Wed Sep 27 20:04:26 2017
Thread-2: Wed Sep 27 20:04:28 2017
Thread-2: Wed Sep 27 20:04:30 2017
Exiting Thread-2
Exiting Main Thread

互斥锁同步线程

不加锁的问题

当不同的线程操作同一批数据的时候,就有可能产生意想不到的问题。比如说:

# encoding: UTF-8
import threading
import time

class MyThread(threading.Thread):
    def run(self):
        global num
        time.sleep(1)
        num = num+1
        msg = self.name+' set num to '+str(num)
        print msg
num = 0
threads = []
def test():
    for i in range(1000):
        t = MyThread()
        t.start()
    threads.append(t)
if __name__ == '__main__':
    test()
    for t in threads:
        t.join()
    print num

理论上说,一千个线程都让num增1,结果应该是1000,但是输出的结果不是1000:(而且每次都不一样)

............
Thread-998 set num to 905
Thread-988 set num to 906
Thread-994 set num to 904
Thread-992 set num to 901
Thread-990 set num to 907
Thread-1000 set num to 908
908

这是因为不同线程同时操作同样数据导致的线程不同步。此时需要通过加互斥锁实现线程的同步。

加锁的效果

# encoding: UTF-8
import threading
import time

class MyThread(threading.Thread):
    def run(self):
        global num
        time.sleep(1)
    threadLock.acquire() 
        num = num+1
    threadLock.release()
        msg = self.name+' set num to '+str(num)
        print msg
num = 0
threadLock = threading.Lock()
threads = []
def test():
    for i in range(1000):
        t = MyThread()
        t.start()
    threads.append(t)
if __name__ == '__main__':
    test()
    for t in threads:
        t.join()
    print num

加锁相当于每个数据在每一时刻只能被一个线程获取到,而获取不到锁的线程就会处于暂时的阻塞状态。输出结果是这样的:

.........
Thread-994 set num to 994
Thread-995 set num to 995
Thread-993 set num to 993
Thread-996 set num to 996
Thread-999 set num to 997
Thread-1000 set num to 998
Thread-998 set num to 999
Thread-997 set num to 1000
1000

可以看出,并不是按照线程的创建时间获取数据的处理权限,但是结果却可以保证每个线程都能依次处理数据,从而保证数据被自增了1000次。

<think>我们正在讨论如何使用Python实现多线程或多进程来消费Kafka消息。根据引用[1],我们可以使用kafka-python库来创建Kafka消费者。同时,引用[2]和[3]提到了多线程消费Kafka的情况,但需要注意的是,Kafka消费者本身不是线程安全的,因此不能直接在多个线程中共享同一个消费者实例。但是,我们可以通过创建多个消费者实例(每个线程一个)来实现多线程消费,或者使用进程(每个进程一个消费者)来实现多进程消费。下面我将分别介绍多线程和多进程的实现方式,并给出代码示例。1.多线程消费Kafka思路:创建多个消费者线程,每个线程运行一个独立的Kafka消费者实例,订阅相同的topic(或不同的topic,根据需求),并消费消息。注意,如果多个消费者属于同一个消费者组,那么它们会协同消费同一个topic的不同分区,实现负载均衡;如果属于不同的组,则每个消费者都会收到所有消息(广播模式)。2.多进程消费Kafka思路:与多线程类似,每个进程运行一个独立的Kafka消费者实例。由于进程间内存隔离,这种方式更安全,但进程间通信和资源消耗相对较大。在实现之前,请确保已经安装kafka-python库:pipinstallkafka-python下面是一个多线程消费的示例代码。我们假设要消费多个topic,并且每个线程消费一个topic(或者多个线程共同消费同一个topic的不同分区,这里我们按同一个消费者组来消费多个topic,每个线程处理一个topic的消息)。但注意,实际上一个消费者可以同时订阅多个topic,而多线程通常用于提高处理消息的能力(每个线程处理一个分区或一个topic的消息)。这里我们展示每个线程负责一个topic的场景。然而,更常见的做法是让多个线程(每个线程一个消费者实例)共同消费同一个topic(可能包含多个分区),因为Kafka的分区机制可以支持多个消费者并行消费。但为了灵活性,我们也可以让每个线程处理不同的topic。以下代码示例中,我们创建多个线程,每个线程负责消费一个指定的topic(也可以一个线程消费多个topic,但这里为了简单,每个线程一个topic)。注意:如果多个线程属于同一个消费者组,那么同一个topic的消息会被分配到不同线程(消费者)上(按分区分配),而不同topic的消息则会被不同的线程处理。但是,请注意:一个消费者组可以订阅多个topic,每个消费者(线程)会分配到组内订阅的所有topic中的某些分区。因此,如果希望每个topic由独立的消费者组处理,那么应该为每个topic创建独立的消费者组(即每个线程使用不同的group_id)?这取决于需求。不过,通常我们可能希望同一个消费者组内的消费者共同消费多个topic,那么每个消费者可能会同时消费多个topic的消息。因此,我们有两种设计:设计1:每个线程(消费者)只消费一个topic,并且使用不同的消费者组(这样每个topic的消息都会被独立消费,互不影响)。设计2:所有线程属于同一个消费者组,共同消费多个topic(这样每个线程可能会消费多个topic的消息,需要在线程内部根据消息的topic进行分发处理)。根据问题,用户希望消费多个topic,所以我们采用设计2:同一个消费者组同时消费多个topic,多个消费者线程共同处理这些topic的消息。但是,如果希望每个topic由专门的线程处理,我们可以为每个topic创建一个消费者组,然后每个消费者组启动一个或多个线程(每个线程一个消费者实例)来消费该topic。这样不同topic之间互不影响。这里我们先展示设计2(同一个消费者组,多个线程共同消费多个topic)的代码。注意:由于Kafka消费者不是线程安全的,所以我们每个线程创建自己的消费者实例,并且这些消费者属于同一个消费者组。多线程示例(同一个消费者组,每个线程一个消费者实例,共同消费多个topic):注意:Kafka会为同一个消费者组内的消费者分配订阅的topic的分区。因此,多个线程(多个消费者实例)会共同分担所有topic的所有分区的消息。然而,如果希望每个线程只处理特定的topic,那么我们需要在消费者线程内部根据消息的topic进行过滤,将不属于该线程的topic的消息忽略?但这样会造成浪费(因为消息已经被消费了,只是被忽略)。所以更好的做法是每个线程只订阅自己关心的topic(即每个消费者实例只订阅一个topic,并且使用不同的消费者组)。这样每个线程只接收自己订阅的topic的消息。因此,我们调整方案:为每个topic创建一个独立的消费者组,并启动一个线程(或多个线程,如果该topic有多个分区)来消费。这样每个线程只处理一个topic。下面我们分别给出两种方式的示例。方式一:同一个消费者组,多个线程共同消费多个topic(每个线程可能处理多个topic的消息,需要根据topic进行判断)方式二:每个topic一个消费者组,每个topic使用一个线程(或多个线程)消费。由于问题中没有明确,我们这里选择方式一(因为引用[1]中提到消费多个topic,通常使用同一个消费者组订阅多个topic)和方式二(每个topic独立)都会给出。先给出方式一(同一个消费者组)的代码:注意:在同一个消费者组中,每个消费者实例(每个线程)会分配到多个topic中的一些分区。因此,每个线程都会收到不同topic的消息。我们需要在线程内部根据消息的topic进行不同的处理。示例代码(方式一):</think>### 如何使用Python实现多线程/多进程消费Kafka消息 在Python中实现多线程或多进程消费Kafka消息,主要使用`kafka-python`库。以下是两种高效实现方式: #### 一、多线程消费实现 Kafka消费者非线程安全,需为每个线程创建独立消费者实例。推荐使用线程池管理消费者: ```python from kafka import KafkaConsumer from concurrent.futures import ThreadPoolExecutor def consume_topic(topic): consumer = KafkaConsumer( topic, bootstrap_servers='localhost:9092', group_id='my-group', auto_offset_reset='earliest' ) for message in consumer: print(f"Thread-{topic}: {message.value.decode('utf-8')}") if __name__ == "__main__": topics = ['topic1', 'topic2', 'topic3'] # 要消费的topic列表 with ThreadPoolExecutor(max_workers=len(topics)) as executor: executor.map(consume_topic, topics) ``` **关键点:** 1. 每个线程独立消费者实例 2. 线程数通常等于topic数量 3. 相同`group_id`实现负载均衡[^1] 4. 使用`auto_offset_reset`控制起始位置 #### 二、多进程消费实现 多进程更适合CPU密集型处理,利用`multiprocessing`模块: ```python from kafka import KafkaConsumer from multiprocessing import Process def process_consumer(topic): consumer = KafkaConsumer( topic, bootstrap_servers='localhost:9092', group_id='multi-process-group' ) for msg in consumer: # 执行CPU密集型处理 result = heavy_processing(msg.value) print(f"Process-{topic}: {result}") if __name__ == "__main__": topics = ['logs', 'metrics', 'events'] processes = [] for topic in topics: p = Process(target=process_consumer, args=(topic,)) p.start() processes.append(p) for p in processes: p.join() ``` **优势:** 1. 避免GIL限制,提升CPU利用率 2. 进程间完全隔离,更高容错性 3. 适合长时间运行任务[^2] #### 三、高级优化技巧 1. **动态分区分配** 使用`consumer.partitions_for_topic()`获取分区信息,实现精细控制 ```python partitions = consumer.partitions_for_topic('target_topic') consumer.assign([TopicPartition('target_topic', p) for p in partitions]) ``` 2. **批量提交偏移量** 启用`enable_auto_commit=False`手动提交: ```python batch_size = 100 for i, msg in enumerate(consumer): if i % batch_size == 0: consumer.commit() ``` 3. **消费者心跳配置** 防止被误认为离线: ```python consumer = KafkaConsumer( ..., session_timeout_ms=30000, heartbeat_interval_ms=5000 ) ``` #### 四、性能对比表 | 方式 | 适用场景 | 吞吐量 | 资源消耗 | 复杂度 | |------------|------------------|--------|----------|--------| | 单线程 | 低流量测试 | 低 | 低 | ★☆☆☆☆ | | **多线程** | I/O密集型任务 | 高 | 中 | ★★★☆☆ | | **多进程** | CPU密集型处理 | 极高 | 高 | ★★★★☆ | | 协程 | 超高并发连接 | 极高 | 极低 | ★★★★★ | > **实际建议**:多数场景优先选择多线程方案,仅在CPU处理成为瓶颈时使用多进程[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值