无法理解TensorOverflow train.QueueRunner的结果

时间:2019-09-03 04:04:50

标签: python multithreading tensorflow queue

对不起,我是TensorFlow的初学者。以下只是一个TensorFlow的代码,它使用两个线程独立地入队和出队。

import tensorflow as tf

Q = tf.compat.v1.FIFOQueue(1000, tf.float32)
var = tf.Variable(0.0)
data = tf.compat.v1.assign_add(var, tf.constant(1.0))
en_q = Q.enqueue(data)
qr = tf.train.QueueRunner(Q, enqueue_ops=[en_q])
init_op = tf.compat.v1.global_variables_initializer()
with tf.compat.v1.Session() as sess:
    sess.run(init_op)
    coord = tf.train.Coordinator()
    threads = qr.create_threads(sess, coord=coord, start=True)
    for i in range(300):
        print(sess.run(Q.dequeue()))
    coord.request_stop()
    coord.join(threads)

结果如下:

3.0
7.0
10.0
14.0
18.0
21.0
26.0
....

我对这个结果很困惑。由于Q是FIFO队列,因此即使出队和入队在两个不同的线程中,入队的数目仍应为1,2,3,4,5,6,...。为什么出队号码可以是3,7,10,14,....,数字1, 2, 4, 5, ...在哪里?

1 个答案:

答案 0 :(得分:0)

随机性与FIFOqueue无关,但与线程执行的并发性是预期的行为。肯定涉及的线程不止2个。

为简化起见,让我们尝试在没有for循环的情况下运行代码,添加一些print语句和sleep间隔,然后看看会发生什么:

import tensorflow as tf
import time

Q_size = 1000 # Q size
t = 1 # t in sec

Q = tf.compat.v1.FIFOQueue(Q_size, tf.float32)
var = tf.Variable(0.0)
data = tf.compat.v1.assign_add(var, tf.constant(1.0))
en_q = Q.enqueue(data)

qr = tf.train.QueueRunner(Q, enqueue_ops=[en_q])

init_op = tf.compat.v1.global_variables_initializer()

with tf.compat.v1.Session() as sess:
    sess.run(init_op)
    coord = tf.train.Coordinator()

    print('*** before qr.create_threads ***')
    print('Q.size()', sess.run(Q.size()))
    print('var', var.eval())

    threads = qr.create_threads(sess, coord=coord, start=True)

    print('*** after qr.create_threads ***')
    time.sleep(t) # sleep t sec in main to wait for all threads to finish running.
    print('Q.size()', sess.run(Q.size()))
    print('var', var.eval())

    coord.request_stop()
    coord.join(threads)    

输出:

*** before qr.create_threads ***
Q.size() 0
var 0.0

*** after qr.create_threads ***
Q.size() 1000
var 1001.0

在调用qr之前,什么都没有发生。 FIFOQueue Qvar中的值仍然等于0。这是预期的。

调用qr.create_threads并设置start=True后,qr执行以下操作:

  1. 开始使用您的Q(在本例中为enqueue_ops)填充en_q。它将尝试打包尽可能多的en_q操作,这取决于您的Q大小。
  2. en_q中的每个Q创建线程。
  3. 同时运行线程。 (在这一点上,考虑到Q的大小,很明显,正如OP所提到的,仅涉及2个线程。)

引入sleep是为了允许所有这些线程在我们显示输出之前完成执行。我们想知道Q创建的所有线程完成运行后var的大小和qr的值是什么。

因此,正如预期的那样,Q被填充了1000次en_q操作,而var被添加了1001次。

现在,如果我们删除sleep间隔,我们将得到如下所示的随机输出。

不带sleep的随机输出:

*** before qr.create_threads ***
Q.size() 0
var 0.0

*** after qr.create_threads ***
Q.size() 32
var 35.0

以上随机输出的意思是,当我们在主线程中显示输出时,qr仍在填充Q,在后台同时创建和运行线程。

现在,让我们将for循环放回去,同时在循环中引入另一个sleep间隔:

import tensorflow as tf
import time

Q_size = 1000 # Q size
N =10 # range of for loop
t = 1 # t in secs

Q = tf.compat.v1.FIFOQueue(Q_size, tf.float32)
var = tf.Variable(0.0)
data = tf.compat.v1.assign_add(var, tf.constant(1.0))
en_q = Q.enqueue(data)

qr = tf.train.QueueRunner(Q, enqueue_ops=[en_q])

init_op = tf.compat.v1.global_variables_initializer()

with tf.compat.v1.Session() as sess:
    sess.run(init_op)
    coord = tf.train.Coordinator()

    print('*** before qr.create_threads ***')
    print('Q.size()', sess.run(Q.size()))
    print('var', var.eval())

    threads = qr.create_threads(sess, coord=coord, start=True)

    print('*** after qr.create_threads ***')
    time.sleep(t) # sleep t sec in main to wait for all threads to finish running.
    print('Q.size()', sess.run(Q.size()))
    print('var', var.eval())

    print('*** for loop ***')
    for i in range(N):
        sess.run(Q.dequeue())
        time.sleep(t) # sleep t sec in main to wait for all threads to finish running.
        print('var', var.eval())
        print('.size()', sess.run(Q.size()))

    coord.request_stop()
    coord.join(threads)    

引入sleep可以使后台线程在我们在主线程中显示变量之前完成操作。

您可以在下面的输出中看到,var现在依次增加,而Q的大小保持为1000。每个sess.run(Q.dequeue())调用现在从{{1 }},将1加到en_q

我希望这可以清除您观察到的随机性与Q是否为var无关。随机性是由线程并行性的预期行为引起的。

输出:

Q