強化学習の実装:競合シナリオでのロックメカニズムに基づくアイドルポート検索

この記事は最初に公開されました:Walker AI

強化学習のゲーム分野では、複雑なロジックを備えた実際のゲームをブラックボックスと見なし、ネットワーク通信を使用してデータと対話し、トレーニングの目的を達成することがよくあります。トレーニングの効率を上げるために、Pythonでは複数のプロセスを使用して複数のアクターの役割を果たしてゲームのブラックボックスとやり取りすることがよくあります。したがって、このような環境では、複数のプロセスが同じ通信ポートをめぐって競合する可能性が非常に高くなります(レースシナリオ)。 )、プロセスにエラーを報告させるのは簡単です。

上記の背景に基づいて、この記事では最初に従来のソリューションを提供し、次に従来の方法の制限を分析し、次にロックメカニズムに基づくソリューションを紹介し、最後に実際のpythonルーチンを提供します。この記事は主に次の3つの部分に分かれています。

(1)従来のソリューション

(2)ロック機構に基づくアイドルポートの検索と使用

(3)Pythonコードの実装

1.従来のソリューション

上記の問題については、最も考えやすい解決策が1つあります。プロセスがポートを呼び出すたびに、ポート番号がすでに使用されているかどうかを確認します。使用されている場合は、スキップして、未使用のポート番号になるまで次のポートの検索を続けます。が見つかりました。呼び出し元に返されます。このプロセスをPythonで実装するだけです。

import socket 

def get_free_port(ip, init_port): 
    while True: 
        try:
            sock = socket.socket()
            sock.bind((ip, init_port))
            ip, port = sock.getnameinfo()
            sock.close()
            return port
        except Exception:
            init_port += 1
            continue

复制代码

上記のアイデアを少し分析した後、大まかに3つのステップに分けることができます。

(1)このsock.bind()機能を使用して、ポートを自動的にバインドし、ポートが使用可能かどうかを判別します。

(2)ポートが使用可能であることを証明するためにポートをバインドできる場合は、ポートを使用してsock.close()解放します。バインドできない場合は、ポートが使用されたことを証明し、ポート番号に1を追加して、次の操作を続行します。成功するまでバインドしてから、同じものを使用しsock.close()てポートを解放します。;

(3)解放されたポート番号を呼び出す必要のあるプログラムに戻します。

上記のアイデアはうまくいくようですが、それだけでは十分ではありません。このアイデアを使用すると、ポートが非常に短時間でバインドされるのを防ぐことができるだけであり、競争シナリオでの使用に対応することは困難です。レース環境では、プロセスの数が増えると、次の状況が発生する可能性が高くなります。


上の図に示すように、同じポート番号XがプロセスAとプロセスBで使用されます。プロセスBの場合、チェックの進行はプロセスAの進行よりもわずかに遅くなります。プロセスAがポートXを解放し、バインドのためにプログラムに戻る準備をすると、プロセスBは検査のためにポートXをバインドし、プロセスAはそれを実行できなくなります。ポートXをバインドします。エラーが発生します。

因此可以看出,竞态场景中如何返回一个安全的端口号并不简单,即使利用随机种子随机初始端口号也不能彻底避免这种情况;我们需要一种机制保证一个未绑定的端口号不能被其它进程任意绑定。

2. 基于锁机制的闲置端口查用

使用lock file能够保证即使端口号未绑定,在未拿到锁之前也不会被其他进程绑定。利用fasteners.process_lock.InterProcessLock对端口加锁,具体思路如下:

(1)sock.bind()自动绑定检测端口是否可用;

(2)若端口可用则用InterProcessLock对端口加锁;否则继续检查,直到检测到可用端口后加锁;

(3)解除之前的端口绑定;

(4)安全返回这个端口号;

(5)对该端口解锁;

流程如下:

回顾上述过程,在端口X被绑定后的每一个步骤之间,端口都是安全的。

3. python代码实现

首先定义两个类class BindFreePort()class FreePort(),前者用于搜索并检测可用端口;后者输入前者的检测结果,使用第2章所述的机制将端口安全的输出给需要的程序。

import os
import fasteners
import threading


class BindFreePort(object):
    def __init__(self, start, stop):
        self.port = None

        import random, socket

        self.sock = socket.socket()

        while True:
            port = random.randint(start, stop)
            try:
                self.sock.bind(('127.0.0.1', port))
                self.port = port
				import time
				time.sleep(0.01)
                break
            except Exception:
                continue

    def release(self):
        assert self.port is not None
        self.sock.close()
    

class FreePort(object):
    used_ports = set()

    def __init__(self, start=4000, stop=6000):
        self.lock = None
        self.bind = None
        self.port = None

        from fasteners.process_lock import InterProcessLock
        import time
        pid = os.getpid()

        while True:
            bind = BindFreePort(start, stop)

            print(f'{pid} got port : {bind.port}')

            if bind.port in self.used_ports:
                print(f'{pid} will release port : {bind.port}')
                bind.release()
                continue

            '''
            Since we cannot be certain the user will bind the port 'immediately' (actually it is not possible using
            this flow. We must ensure that the port will not be reacquired even it is not bound to anything
            '''
            lock = InterProcessLock(path='/tmp/socialdna/port_{}_lock'.format(bind.port))
            success = lock.acquire(blocking=False)

            if success:
                self.lock = lock
                self.port = bind.port
                self.used_ports.add(bind.port)
                bind.release()
                break

            bind.release()
            time.sleep(0.01)

    def release(self):
        assert self.lock is not None
        assert self.port is not None
        self.used_ports.remove(self.port)
        self.lock.release()
复制代码

然后,基于这两个类的功能,我们简单测试一下:

def get_and_bind_freeport(*args):
    freeport = FreePort(start=4000, stop=4009)
	import time
	time.sleep(0.5)
    return freeport.port

def test():
    from multiprocessing.pool import Pool
    jobs = 10
    p = Pool(jobs)
    ports = p.map(get_and_bind_freeport, range(jobs))
    print(f'[ports]: {ports}')
    assert len(ports) == len(set(ports))
    p.close()

if __name__ == '__main__':
    test()
复制代码

上述代码中,我们构建了函数get_and_bind_freeport()按照第2章所述机制返回一个端口,用time.sleep(0.5)模拟进程内的时间扰动,其中端口的搜索范围是4000~4009;函数test()从进程池中启动10个进程,每个进程映射一个函数get_and_bind_freeport()4000~4009中搜索一个端口号并将之安全返回。

如果整个过程中端口号是安全的,那么返回结果应当是len(ports) == len(set(ports))即10个端口分别被10个进程查用,不存在多个进程返回同一端口号的情况。

4. 总结

本論文では、実際のゲームの強化学習訓練における通信ポートのマルチプロセス競争の現象を解決するための2つの方法を比較します。原理分析と比較および実践的な実地実験を通じて、通信ポート番号は次のようになる可能性があるという結論に達しました。競争シナリオでチェックして使用します。ロックメカニズムに基づくポートチェックの従来の考え方と比較して、このシナリオにより安全に適用できます。同時に、ゲーム分野で強化学習を実施する過程で多くの実際的な問題があることを思い出させます。実際のエンジニアリングでの継続的な実践と要約によってのみ、目標を達成することができます。

5.リファレンス

[1]ファスナー

[2]プロセス間の同期


PS:より技術的な乾物については、[パブリックアカウント| xingzhe_ai]に注意を払い、歩行者と話​​し合ってください。

おすすめ

転載: juejin.im/post/6966068413567500318