【问题标题】:Multiprocessing python program inside DockerDocker内的多处理python程序
【发布时间】:2018-06-07 22:27:20
【问题描述】:

我正在尝试在 docker 容器内测试 python 的多处理,但即使成功创建了进程(我有 8 个 CPU 并创建了 8 个进程),它们也总是只占用一个物理 CPU。 这是我的代码:

from sklearn.externals.joblib.parallel import Parallel, delayed
import multiprocessing
import pandas
import numpy
from scipy.stats import linregress
import random
import logging

def applyParallel(dfGrouped, func):
    retLst = Parallel(n_jobs=multiprocessing.cpu_count())(delayed(func)(group) for name, group in dfGrouped)
    return pandas.concat(retLst)

def compute_regression(df):
    result = {}

    (slope,intercept,rvalue,pvalue,stderr) = linregress(df.date,df.value)
    result["slope"] = [slope]
    result["intercept"] = [intercept]

    return pandas.DataFrame(result)

if __name__ == '__main__':
    logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    logging.info("start")
    random_list = []
    for i in range(1,10000):
        for j in range(1,100):
            random_list.append({"id":i,"date":j,"value":random.random()})

    df = pandas.DataFrame(random_list)

    df = applyParallel(df.groupby('id'), compute_regression)

    logging.info("end")

当我像 --cpus 或 --cpuset 这样启动时,我尝试了多个 docker 选项,但它始终只使用 1 个物理 CPU。 这是 Docker、python、操作系统中的问题吗? Docker 版本是 1.13.1

cpu_count() 的结果:

>>> import multiprocessing
>>> multiprocessing.cpu_count()
8

在运行期间,这是一个顶部。我们可以看到主进程和 8 个子进程,但我发现百分比很奇怪。

然后,如果我更改为 4 个进程,则使用的 CPU 总量始终相同:

【问题讨论】:

  • 如果您在 Mac 或 Windows 上运行 Docker,它会在虚拟机内部运行。您需要将 Docker 作为一个整体进行配置,以便为该 VM 分配更多 CPU。 docker run 的选项不会覆盖它,你不能只使用 VM 允许使用的数量。
  • 它实际上在 Linux 中运行 :(
  • 你能做一个print(multiprocessing.cpu_count()) 并将结果添加到你的问题中吗?
  • @hansaplast 我添加了截图
  • 是来自 docker 内部的吗?

标签: python docker python-multiprocessing


【解决方案1】:

来自https://docs.docker.com/get-started - “从根本上说,容器只不过是一个正在运行的进程,它应用了一些附加的封装功能,以使其与主机和其他容器隔离。”

Docker 在主机上运行。该主机(或虚拟机)具有一定数量的物理(或虚拟)CPU。 multiprocessing.cpu_count() 在您的情况下显示 8 的原因是因为这是您系统的 CPU 数量。使用像 --cpus--cpuset-cpus 这样的 docker 选项不会改变你机器的硬件,这是 cpu_count() 所报告的。

在我当前的系统上:

# native
$ python -c 'import multiprocessing as mp; print(mp.cpu_count())'
12
# docker
$ docker run -it --rm --cpus 1 --cpuset-cpus 0 python python -c 'import multiprocessing as mp; print(mp.cpu_count())'
12

来自https://docs.docker.com/config/containers/resource_constraints/#cpu - “默认情况下,每个容器对主机 CPU 周期的访问是无限制的。” 但是您可以使用 --cpus--cpuset-cpus 等选项来限制容器。

--cpus 可以是一个浮点数,最大为可用物理 CPU 的数量。您可以将此数字视为分数<--cpus arg>/<physical CPU's> 中的分子。如果您有 8 个物理 CPU 并指定 --cpus 4,那么您告诉 docker 使用的 CPU 不超过总 CPU 的 50% (4/8)。 --cpus 1.5 将使用 18.75% (1.5/8)。

--cpuset-cpus 实际上确实限制了要使用的物理/虚拟 CPU。

(Docker 的文档中还介绍了许多其他与 CPU 相关的选项。)

这是一个较小的代码示例:

import logging
import multiprocessing
import sys

import psutil
from joblib.parallel import Parallel, delayed

def get_logger():
    logger = logging.getLogger()
    if not logger.hasHandlers():
        handler = logging.StreamHandler(sys.stdout)
        formatter = logging.Formatter("[%(process)d/%(processName)s] %(message)s")
        handler.setFormatter(formatter)
        handler.setLevel(logging.DEBUG)
        logger.addHandler(handler)
        logger.setLevel(logging.DEBUG)
    return logger

def fn1(n):
    get_logger().debug("fn1(%d); cpu# %d", n, psutil.Process().cpu_num())

if __name__ == "__main__":
    get_logger().debug("main")
    Parallel(n_jobs=multiprocessing.cpu_count())(delayed(fn1)(n) for n in range(1, 101))

在本地和在 docker 中运行它会记录如下行:

[21/LokyProcess-2] fn1(81); cpu# 11
[28/LokyProcess-9] fn1(82); cpu# 6
[29/LokyProcess-10] fn1(83); cpu# 2
[31/LokyProcess-12] fn1(84); cpu# 0
[22/LokyProcess-3] fn1(85); cpu# 3
[23/LokyProcess-4] fn1(86); cpu# 1
[20/LokyProcess-1] fn1(87); cpu# 7
[25/LokyProcess-6] fn1(88); cpu# 3
[27/LokyProcess-8] fn1(89); cpu# 4
[21/LokyProcess-2] fn1(90); cpu# 9
[28/LokyProcess-9] fn1(91); cpu# 10
[26/LokyProcess-7] fn1(92); cpu# 11
[22/LokyProcess-3] fn1(95); cpu# 9
[29/LokyProcess-10] fn1(93); cpu# 2
[24/LokyProcess-5] fn1(94); cpu# 10
[23/LokyProcess-4] fn1(96); cpu# 1
[20/LokyProcess-1] fn1(97); cpu# 9
[23/LokyProcess-4] fn1(98); cpu# 1
[27/LokyProcess-8] fn1(99); cpu# 4
[21/LokyProcess-2] fn1(100); cpu# 5

请注意,我的系统上的所有 12 个 CPU 都在使用中。 请注意

  • 多个进程使用相同的物理 CPU(进程 # 的 22 和 25 使用 cpu#3)
  • 一个单独的进程可以使用多个 CPU(进程 #21 使用 CPU # 的 11 和 9)

使用docker run --cpus 1 ... 运行相同的程序仍会导致所有 12 个进程都使用所有 12 个 CPU,就像不存在 --cpus 参数一样。它只是限制了 docker 允许使用的总 CPU 时间的百分比。

使用docker run --cpusets-cpus 0-1 ... 运行相同的程序将导致启动的所有 12 个进程仅使用 2 个物理 CPU:

[11/LokyProcess-2] fn1(35); cpu# 0
[11/LokyProcess-2] fn1(36); cpu# 0
[12/LokyProcess-3] fn1(37); cpu# 1
[11/LokyProcess-2] fn1(38); cpu# 0
[15/LokyProcess-6] fn1(39); cpu# 1
[17/LokyProcess-8] fn1(40); cpu# 0
[11/LokyProcess-2] fn1(41); cpu# 0
[10/LokyProcess-1] fn1(42); cpu# 1
[11/LokyProcess-2] fn1(43); cpu# 1
[13/LokyProcess-4] fn1(44); cpu# 1
[12/LokyProcess-3] fn1(45); cpu# 0
[12/LokyProcess-3] fn1(46); cpu# 1

要回答“它们总是只占用一个物理 CPU”这一说法——这仅在 --cpusets-cpus 参数恰好/仅 1 个 CPU 时才成立。


(作为旁注——示例中设置日志记录的原因是因为 joblib 中的 open bug。)

【讨论】:

    【解决方案2】:

    multiprocessing.cpu_count() 在我的机器上给出 2 而没有传递--cpu 选项

    前往https://docs.docker.com/engine/admin/resource_constraints/#cpu 了解有关 docker 容器资源的更多信息

    【讨论】:

      【解决方案3】:

      尝试从头开始创建机器(将数值替换为所需的值):

      docker-machine rm default
      docker-machine create -d virtualbox --virtualbox-cpu-count=8 --virtualbox-memory=8192 --virtualbox-disk-size=10000 default
      

      这只是为了安全起见。 现在是重要的部分:

      在运行映像之前指定核心数。以下命令将使用 8 个内核。

      docker run -it --cpuset-cpus="0-7" your_image_name
      

      并检查 docker,如果你不仅在 python 中成功了

      nproc
      

      祝你好运,让我们知道进展如何?!

      【讨论】:

        【解决方案4】:

        您可以通过执行以下命令来测试多处理器是否正常工作:

        $ docker run -it --rm ubuntu:20.04
        root@somehash:/# apt update && apt install stress
        root@somehash:/# stress --cpu 8 # 8 if you have 8 cores
        

        如果您有多个核心,您可以在另一个终端中测试命令htoptop,您应该会看到所有核心都在运行。如果您使用htop,您应该会看到如下内容。

        如果你在这一步。然后一切正常。 此外,当我运行您提供的脚本时,我看到我的处理器正在正常使用,您可以查看下图。 (我还添加了显示它的过程,我在ipython 终端内运行您的脚本。我还将from sklearn.externals.joblib.parallel import Parallel, delayed 更改为from joblib.parallel import Parallel, delayed,因为否则它对我不起作用)。

        我希望提供的信息对您有所帮助。对于其他线索,您可能需要检查您的 docker 版本。

        【讨论】:

          【解决方案5】:
          ''' Distributed load among several Docker containers using Python multiprocessing capabilities '''
          
          import random
          import time
          import subprocess
          import queue
          from multiprocessing import Pool, Queue, Lock
          
          LOCK = Lock()
          TEST_QUEUE = Queue()
          
          
          class TestWorker(object):
              ''' This Class is executed by each container '''
          
              @staticmethod
              def run_test(container_id, value):
                  ''' Operation to be executed for each container '''
          
                  cmd = ['docker exec -it {0} echo "I am container {0}!, this is message: {1}"' \
                          .format(container_id, value)]
                  process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
                  for line in process.stdout:
                      print(line.decode('utf-8')[:-2])
                  process.wait()
          
              @staticmethod
              def container(container_id):
                  ''' Here we get a value from the shared queue '''
          
                  while not TEST_QUEUE.empty():
                      LOCK.acquire()
                      try:
                          value = TEST_QUEUE.get(block=False)
                          time.sleep(0.5)
                      except queue.Empty:
                          print("Queue empty ):")
                          return
                      print("\nProcessing: {0}\n".format(value))
                      LOCK.release()
                      TestWorker.run_test(container_id, value)
          
          def master():
              ''' Main controller to set containers and test values '''
          
              qty = input("How many containers you want to deploy: ")
              msg_val = input("How many random values you want to send among this containers: ")
          
              print("\nGenerating test messages...\n")
              for _ in range(int(msg_val)):
                  item = random.randint(1000, 9999)
                  TEST_QUEUE.put(item)
              ids = []
              for _ in range(int(qty)):
                  container_id = subprocess.run(["docker", "run", "-it", "-d", "centos:7"], \
                          stdout=subprocess.PIPE)
                  container_id = container_id.stdout.decode('utf-8')[:-1]
                  ids.append(container_id)
              pool = Pool(int(qty))
              pool.map(TestWorker.container, ids)
              pool.close()
          
          master()
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2023-02-21
            • 2019-05-20
            • 1970-01-01
            • 2013-04-03
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多