队列和线程

import tensorflow as tf
#创建的图:一个先入先出队列,以及初始化,出队,+1,入队操作  
q = tf.FIFOQueue(3, "float")  
init = q.enqueue_many(([0.1, 0.2, 0.3],))  
x = q.dequeue()  
y = x + 1  
q_inc = q.enqueue([y])  
  
#开启一个session,session是会话,会话的潜在含义是状态保持,各种tensor的状态保持  
with tf.Session() as sess:  
        sess.run(init)  
  
        for i in range(2):  
                sess.run(q_inc)  
  
        quelen =  sess.run(q.size())  
        for i in range(quelen):  
                print (sess.run(q.dequeue()))  

运行的结果是:

0.3
1.1
1.2

上面的例子中入队操作都在主线程中进行。Session中可以多个线程一起运行。 在实际的数据输入的应用场景中,入队操作从硬盘上读取,放到内存当中,速度较慢。

下面的例子进行了改进:
使用QueueRunner可以创建一系列新的线程进行入队操作,让主线程继续使用数据。在训练神经网络的场景中,即训练网络和读取数据是异步的,主线程在训练网络,另一个线程在将数据从硬盘读入内存。

import tensorflow as tf  
import sys  
#创建稍微复杂一点的队列作为例子  
q = tf.FIFOQueue(1000,"float")  
#计数器  
counter = tf.Variable(0.0)  
#操作:给计数器加一  
increment_op = tf.assign_add(counter,tf.constant(1.0))  
#操作:将计数器加入队列  
enqueue_op = q.enqueue(counter)  
  
#创建一个队列管理器QueueRunner,用这两个操作向q中添加元素。目前我们只使用一个线程:  
qr = tf.train.QueueRunner(q,enqueue_ops=[increment_op,enqueue_op]*1)  
#从队列管理器中创建线程,并启动:  
#主线程  
with tf.Session() as sess:  
        sess.run(tf.initialize_all_variables())  
        enqueue_threads = qr.create_threads(sess, start=True)  # 启动入队线程  
        #主线程  
        for i in range(10):  
                print "----------------------:"  
                print (sess.run(q.dequeue())) 

运行结果是:

ERROR:tensorflow:Exception in QueueRunner: Session has been closed.
ERROR:tensorflow:Exception in QueueRunner: Attempted to use a closed Session.

也就是说,当主线程的循环结束后,Session就会自动关闭,相当于main函数已经结束了,QueueRunner还想要使用Session,所以就报错了。

下面的例子加入

import tensorflow as tf  
import sys  
#创建稍微复杂一点的队列作为例子  
q = tf.FIFOQueue(1000,"float")  
#计数器  
counter = tf.Variable(0.0)  
#操作:给计数器加一  
increment_op = tf.assign_add(counter,tf.constant(1.0))  
#操作:将计数器加入队列  
enqueue_op = q.enqueue(counter)  
  
#创建一个队列管理器QueueRunner,用这两个操作向q中添加元素。目前我们只使用一个线程:  
qr = tf.train.QueueRunner(q,enqueue_ops=[increment_op,enqueue_op]*1)  
  
# 主线程  
sess = tf.Session()  
sess.run(tf.initialize_all_variables())  
  
enqueue_threads = qr.create_threads(sess, start=True)  # 启动入队线程  
  
# 主线程  
for i in range(0, 10):  
    print(sess.run(q.dequeue())) 

结果是:

4.0
7.0
10.0
12.0
15.0
17.0
19.0
22.0
26.0
27.0

QueueRunner创建的子线程不在session里,不受session关闭的影响,不会报错,但是运行的结果显然不是我们想要的自然数队列,本质原因是+1操作和入队操作不同步,可能+1操作执行了很多次之后,才会进行一次入队操作。
同时,出队结束后,本应程序要结束,但是因为入队线程没有显示结束,所以,整个程序就跟挂起一样,也结束不了。因为tensorflow是在图上进行计算,要驱动一张图进行计算,必须要送入数据,如果说数据没有送进去,那么sess.run(),就无法执行,tf也不会主动报错提示没有数据送进去,其实tf也不能主动报错,因为tf的训练过程和读取数据的过程其实是异步的。tf会一直挂起,等待数据准备好。现象就是tf的程序不报错,但是一直不动,跟挂起类似,因为没有入队数据驱动了。

下面的例子是加入了协调器的样子:

import tensorflow as tf  
import sys  
#创建稍微复杂一点的队列作为例子  
q = tf.FIFOQueue(1000,"float")  
#计数器  
counter = tf.Variable(0.0)  
#操作:给计数器加一  
increment_op = tf.assign_add(counter,tf.constant(1.0))  
enqueue_op = q.enqueue(counter) # 操作:计数器值加入队列  
#操作:将计数器加入队列  
qr = tf.train.QueueRunner(q, enqueue_ops=[increment_op, enqueue_op] * 1)  
  
# 主线程  
sess = tf.Session()  
sess.run(tf.initialize_all_variables())  
  
#Coordinator:协调器,协调线程间的关系,可以视为一种信号量,用来做同步  
coord = tf.train.Coordinator()  
  
## 启动入队线程, Coordinator是线程的参数  
enqueue_threads = qr.create_threads(sess, coord = coord,start=True)  # 启动入队线程  
  
# 主线程  
for i in range(0, 10):  
    print "-------------------------"  
    print(sess.run(q.dequeue()))  
  
#通知其他线程关闭  
coord.request_stop()  
#其他所有线程关闭之后,这一函数才能返回  
#join操作经常用在线程当中,其作用是等待某线程结束  
coord.join(enqueue_threads)  

运行结果也不是我们想要的:

-------------------------
4.0
-------------------------
8.0
-------------------------
10.0
-------------------------
11.0
-------------------------
15.0
-------------------------
18.0
-------------------------
21.0
-------------------------
24.0
-------------------------
28.0
-------------------------
30.0

关于create_threads(sess, coord=None, daemon=False, start=False)方法的解释:

tf.train.QueueRunner.create_threads(sess, coord=None, daemon=False, start=False)

Create threads to run the enqueue ops.

This method requires a session in which the graph was launched. It creates a list of threads, optionally starting them. There is one thread for each op passed in enqueue_ops.

The coord argument is an optional coordinator, that the threads will use to terminate together and report exceptions. If a coordinator is given, this method starts an additional thread to close the queue when the coordinator requests a stop.

This method may be called again as long as all threads from a previous call have stopped.

意思是:
该方法需要在一个会话中启动图形。该方法创建一个线程列表,可以选择启动它们。在enqueue_ops中,每个op都有一个线程。
coord参数是协调器参数,它一个可选的参数,线程将使用coord来终止并报告异常。如果给定了一个协调器,则该方法在协调器请求停止时启动另一个线程来关闭队列。
只要前面调用的所有线程停止,该方法就可以再次被调用。

Args:
sess: A Session.
coord: Optional Coordinator object for reporting errors and checking stop conditions.
daemon: Boolean. If True make the threads daemon threads.
start: Boolean. If True starts the threads. If False the caller must call the start() method of the returned threads.

Returns:
A list of threads.

你可能感兴趣的:(队列和线程)