Multithreading with Singleton - java

I created a PrintQueue class which does printing related job. I made it singleton, as one network printer is shared by many users, so one instance should be created.
Here is my code:
abstract class Document {
protected String name;
protected String type;
Document(){}
Document(String name){
this.name = name;
}
public String name(){
return this.name;
}
abstract public String type();
}
class TextDocument extends Document {
TextDocument(String name){
super(name);
}
#Override
public String type() {
// TODO Auto-generated method stub
return "text";
}
}
class PdfDocument extends Document {
PdfDocument(String name){
super(name);
}
#Override
public String type() {
// TODO Auto-generated method stub
return "PDF";
}
}
class Node {
public Document document;
public Node next;
Node(Document d){
document = d;
}
}
class PrintQueue {
public Node root;
Node cur;
private static PrintQueue instance;
private PrintQueue(){}
public static synchronized PrintQueue getInstance(){
if(instance == null){
instance = new PrintQueue();
}
return instance;
}
public void push(Document d){
if(root == null){
root = new Node(d);
root.next =null;
}else{
cur = root;
while(cur.next!= null){
cur=cur.next;
}
Node newNode = new Node(d);
cur.next = newNode;
newNode.next = null;
}
}
public Document pop(){
if(root == null){
System.out.println("Queue is empty");
return null;
}else{
Node temp = root;
root=root.next;
System.out.println(temp.document.name()+" "+temp.document.type()+" popped out");
return temp.document;
}
}
public void displayContent(){
if(root == null){
System.out.println("no pending task");
}else{
cur = root;
while(cur!=null){
System.out.println(cur.document.name()+" "+cur.document.type());
cur = cur.next;
}
}
}
#Override
public void run() {
// TODO Auto-generated method stub
}
}
public class test {
public static void main(String[] args) {
Document a= new PdfDocument("loan agreement");
Document b= new TextDocument("Air Ticket");
Document c= new PdfDocument("movie ticket");
Document d= new TextDocument("bike riding");
PrintQueue p = PrintQueue.getInstance();
PrintQueue q = PrintQueue.getInstance();
p.push(a);
p.push(b);
q.push(c);
q.push(d);
p.displayContent();
System.out.println("-----------------------------------");
p.pop();
q.pop();
System.out.println("-----------------------------------");
p.displayContent();
}
}
I want to implement multithreading in my code where the push() method should be synchronized. Otherwise, if a document is sent by multiple users to the printer, it will not be saved in the print queue as expected.
I am new to multithreading, so I was thinking that I should extend Thread class to my printQueue, and in the run() method, I will call push(). However, I am unable to send a parameter to push() in that way and as my printQueue is singleton. I can't initialize document to be passed to push() in the constructor of printQueue.
How can I achieve this?

As far as I understand
you are dealing with consumer and producer problem, which is suitable to use BlockingQueue (example updated from doc) to keep the thread safety:
class DocProducer implements Runnable {
private final BlockingQueue queue;
Producer(BlockingQueue q) { queue = q; }
public void run() {
try {
while (true) { queue.put(produce()); }
} catch (InterruptedException ex) { ... handle ...}
}
Document produce() { ... }
}
class DocConsumer implements Runnable {
private final BlockingQueue queue;
Consumer(BlockingQueue q) { queue = q; }
public void run() {
try {
while (true) { consume(queue.take()); }
} catch (InterruptedException ex) { ... handle ...}
}
void consume(Document x) { ... }
}
class Setup {
void main() {
// or use your own thread-safe queue implementation,
// which is harder to be right, though
BlockingQueue printQueue = new LinkedBlockingQueue();
DocProducer p = new DocProducer(printQueue);
DocConsumer c1 = new DocConsumer(printQueue);
DocConsumer c2 = new DocConsumer(printQueue);
new Thread(p).start();
new Thread(c1).start();
new Thread(c2).start();
}
}
use runnalbe rather than extend thread, or better use ExecutorService
you want your consumer and producer to be multithreaded, which should have multi threads, but not your PrintQueue

Your problem is that you want to avoid calling push() method of your PrintQueue by multiple threads at the same time, then just make that method synchronized -
public synchronized void push(Document d){
if(root == null){
root = new Node(d);
root.next =null;
}else{
cur = root;
while(cur.next!= null){
cur=cur.next;
}
Node newNode = new Node(d);
cur.next = newNode;
newNode.next = null;
}
}
By making this method synchronized, you are ensuring that only one thread can enter inside this method at one time so will make this method thread-safe

Related

How to implement a Trie Iterator with Thread run()

I have an uni assignment where I need to implement a Trie and it's nodes(Node) and the iterator.
The iterator should iterate over the nodes using a StringBuffer to maintain the status of the word and it should implement the Runnable interface, meaning I have to implement a run() method!
I have tried this implementation:
class NodeIterator implements Iterator<String>, Runnable {
String nextWord;
boolean terminated;
Thread thread;
public NodeIterator() {
thread = new Thread(this,"Node iterator");
thread.start();
}
#Override
public void run() {
terminated = false;
visit(Trie.this.root);
synchronized (this) {
terminated = true;
handshake();
}
}
private void visit(Node node) {
}
private void handshake() {
notify();
try {
wait();
} catch (InterruptedException e) {
}
}
#Override
public boolean hasNext() {
synchronized (this) {
if(!terminated)
handshake();
}
return nextWord != null;
}
#Override
public String next() {
String word = nextWord;
synchronized (this) {
word = null;
}
return word;
}
}
I am missing an implementation for the visit() method as I'm not sure how to "visit" the nodes and I'm not sure the rest is correct as I've never worked with threads.
Should I be doing something differently?
Edit:
public class Trie implements Iterable<String> {
Node root;
...
private static class Node {
private HashMap<Character, Node> children;
private boolean endOfWord;
...
}
}
One way you can implement visit() is with recursion. The recursive method needs to keep track of the prefix of the string built so far, and if it finds a node that ends a word, publishes the string it has found so far. Assuming you can't change the signature for visit(Node), you'll need a helper method:
void visit(Node root) {
visitRecursive("", root);
}
private void visitRecursive(String prefix, Node node) {
if (node.endOfWord) {
nextWord = prefix;
synchronized (this) {
handshake();
}
}
for (Map.Entry<Character, Node> entry : node.children.entrySet()) {
visitRecursive(prefix + entry.getKey(), entry.getValue());
}
}
I'm not sure all your multithreading code is correct, but the very least this will iterate over all strings stored in the trie.

Dequeueing from a generic type queue, null pointer exception?

I am trying to make it dequeue the front node and print it out, however I am getting the following error:
Exception in thread "main" java.lang.NullPointerException
at MyQueue.toString(MyQueue.java:25)
at TestQueue.main(TestQueue.java:13)
From the TestQueue class, I am expecting the outputs 1 then after dequeueing 1 again, then finally 2 from the second toString call.
public class TestQueue {
public static void main(String[] args) {
MyQueue<String> qStr = new MyQueue();
qStr.enqueue("1");
qStr.enqueue("2");
qStr.enqueue("3");
qStr.enqueue("4");
qStr.enqueue("5");
qStr.toString(qStr.front);
qStr.dequeue();
qStr.toString(qStr.front);
}
}
public class MyQueue<T>{
MyNode<T> back;
MyNode<T> front;
public MyQueue(){
back = null;
front = null;
}
public void enqueue(T payload) {
if(back == null) {
MyNode<T> firstnode = new MyNode<T>(payload);
back = firstnode;
front = firstnode;
}
else {
MyNode<T> addtoback = new MyNode<T>(payload, back.next, null);
back = addtoback;
}
}
public String toString(MyNode<T> x) {
System.out.println(x.payload);
if(x.payload == null) {
return "";
}
else{
return (String) x.payload;
}
}
public void dequeue() {
if (isEmpty()) {
throw new RuntimeException("Queue underflow");
} else if (front == back) {
T payload1 = front.payload;
front = null;
back = null;
System.out.println(payload1);
}
T payload1 = front.payload;
front = front.previous;
System.out.println(payload1);
}
public Boolean isEmpty() {
if(back==null) {
return true;
}
return false;
}
public int size() {
MyNode<T> k = back;
if(isEmpty()||k.next==null) {
return 0;
}
k = k.next;
return 1+size();
}
You never set the previous variable in the MyNode class.
What is happening is that you set the front variable in the MyQueue class when you call enqueue(T) for the first time. Once you call dequeue() for the first time it sets the front variable in the MyQueue to the previous variable in the MyNode class which is never set. front in the MyQueue class becomes null and the next time toString(MyNode<T>) is called you get a NullPointerException.
That is also why the first call to toString(MyNode<T>) doesn't give a NullPointerException. dequeue() wasn't called yet.

printing a linked list using for loop in java

I am learning linked list and wrote a sample code to understand the fundamentals. My code works, but is there another way to print out the list using a for loop without the while loop?
I cheated using the for loop I made, because I already knew the number of nodes in the list. Is there a different way of printing the list using a for loop?
public class FriendNode {
FriendNode next;
String name;
FriendNode(String name)
{
this.name = name;
this.next = null;
}
public FriendNode(String name, FriendNode n)
{
this.name = name;
this.next = n;
}
public FriendNode getNext()
{
return this.next;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
FriendNode g = new FriendNode("Bob");
FriendNode o = new FriendNode("Alice");
FriendNode k = new FriendNode("Tom");
FriendNode m = new FriendNode("Day");
g.next = o;
o.next = k;
k.next = m;
m.next = null;
FriendNode current=g;
while(current!=null)
{
System.out.println(current);
current = current.next;
}
for(int i =0; i<4;i++)
{
System.out.println(current);
current = current.next;
}
}
}
You can do it this way :
for (FriendNode current=g; current != null; current = current.next) {
System.out.println(current);
}
This is assuming that g is the first node, since that's how you initialized current when printing the list with the while loop.
It is essentially doing the same as the while loop, except that the initialization and increment are moved to the for expression, which make it more compact.
The for loop doesn't have to work purely with ints, nor does it have to be incremented or decremented. This is also valid:
for (FriendNode ii = g; ii != null; ii = ii.next)
{
System.out.println(ii);
}
The potential problem with both, though, is that you run the risk of an infinite loop - if you set m.next to g, both the while loop and the for loop will execute forever. If you needed to, you could guard against that by keeping a reference to the FriendNode (g) you've started with, and breaking out of the loop if i is g.
You can implement Iterable and use "the other kind" of for loop
for (Friend f : new FriendList(g)) {
System.out.println(f.name);
}
I've created a FriendList that uses the FriendNode. And stuck a Friend object inside the FriendNode rather than just a string. IMO that will allow you better extensiblity going forwards.
The implementation looks like this:
import FriendList.Friend;
public class FriendList implements Iterable<Friend> {
public static class Friend {
public Friend(String name) {
this.name = name;
}
String name;
}
public static class FriendNode {
FriendNode next;
Friend friend;
FriendNode(String name)
{
this.friend = new Friend(name);
this.next = null;
}
public FriendNode(String name, FriendNode n)
{
this.friend = new Friend(name);
this.next = n;
}
public FriendNode getNext()
{
return this.next;
}
}
public FriendList(FriendNode n) {
first = n;
}
#Override public Iterator<Friend> iterator() {
return new Iterator<Friend>() {
FriendNode node = first;
#Override public boolean hasNext() {
return node != null;
}
#Override public Friend next() {
Friend f = node.friend;
node = node.next;
return f;
}
#Override public void remove() {
throw new UnsupportedOperationException();
}
};
}
FriendNode first;
public static void main(String[] args) {
// TODO Auto-generated method stub
FriendNode g = new FriendNode("Bob");
FriendNode o = new FriendNode("Alice");
FriendNode k = new FriendNode("Tom");
FriendNode m = new FriendNode("Day");
g.next = o;
o.next = k;
k.next = m;
m.next = null;
FriendList list = new FriendList(g);
for (Friend f : list) {
System.out.println(f.name);
}
}
}

ConcurrentLinkQueue implementation going into deadlock

I am learning concurrent programming and wrote this concurrentLinkeQueue using AtomicReference.
Following Example goes into Deadlock. Please see.
package concurrent.AtomicE;
import java.util.concurrent.atomic.AtomicReference;
public class ConcurrentLinkQueue<V> {
private AtomicReference<Node> head = new AtomicReference<Node>();
public void offer(final V data) {
final Node<V> newNode = new Node<V>(data,Thread.currentThread().getName());
System.out.println("*********** NEW "+ newNode);
AtomicReference<Node> pointer = head;
for(;;){
if(pointer.get() == null){ // Threads wait here for infinite time
final boolean success = pointer.compareAndSet(null,newNode);
System.out.println(Thread.currentThread().getName() +" " + success);
if(success)
{
System.out.println(Thread.currentThread().getName() +"Returning");
return;
}else{
final Node<V> current = pointer.get();
pointer = current.next;
System.out.println(Thread.currentThread().getName() +" Next Pointer");
}
}
}
}
public void printQueueData(){
AtomicReference<Node> pointer = head;
for(;pointer!=null;){
final Node node = pointer.get();
System.out.println(node);
pointer = node.next;
}
}
private static class Node<V>{
private AtomicReference<Node> next;
private volatile V data = null;
private String threadName = "";
Node(V data1,String threadName){
this.data = data1;
this.threadName = threadName;
}
#Override
public String toString() {
return "threadName=" + threadName +
", data=" + data;
}
private AtomicReference<Node> getNext() {
return next;
}
private void setNext(AtomicReference<Node> next) {
this.next = next;
}
private V getData() {
return data;
}
private void setData(V data) {
this.data = data;
}
}
}
package concurrent.AtomicE;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class Main {
private static final ConcurrentLinkQueue<Integer> clq = new ConcurrentLinkQueue<Integer>();
public static void main(String[] args) throws InterruptedException {
Task t = new Task();
Thread t1 = new Thread(t); t1.setName("t1");
Thread t2 = new Thread(t); t2.setName("t2");
//Thread t3 = new Thread(t); t3.setName("t3");
//Thread t4 = new Thread(t); t4.setName("t4");
//Thread t5 = new Thread(t); t5.setName("t5");
t1.start();
t2.start();
//t3.start();
//t4.start();
//t5.start();
t1.join();
t2.join();
//t3.join();
//t4.join();
//t5.join();
}
private static class Task implements Runnable{
#Override
public void run() {
for(int i=0;i<5;++i){
clq.offer(i);
}
}
}
}
after taking thread dump it shows that threads wait forever at following line
if(pointer.get() == null){ // Threads wait here for infinite time
can you please help why threads wait here forever?
[EDIT]
Solved it --->
public class ConcurrentLinkQueue<V> {
private final AtomicReference<Node> firstNodePointer = new AtomicReference<Node>();
public void offer(final V data) {
final Node<V> newNode = new Node<V>(data,Thread.currentThread().getName());
System.out.println(newNode);
final Node<Integer> firstNode = firstNodePointer.get();
if(firstNode == null){
if(firstNodePointer.compareAndSet(null,newNode) == true)
return;
}
boolean success = false;
Node<Integer> nodePointer = firstNode;
AtomicReference<Node> atomicRefPointer = firstNodePointer;
while(!success){
atomicRefPointer = nodePointer.getNext();
if(atomicRefPointer.get() == null){
success = atomicRefPointer.compareAndSet(null,newNode);
}else{
nodePointer = atomicRefPointer.get();
}
}
}
}
Another Solution->
public void fastOffer(final V data){
final Node<V> newNode = new Node<V>(data,Thread.currentThread().getName());
System.out.println(newNode);
AtomicReference<Node> pointer = firstNodePointer;
for(;;){
if(pointer.compareAndSet(null,newNode)){
return;
}
pointer = pointer.get().getNext();
}
}
In your example condition pointer.get() == null always returns false excepts first case when you assign it to head, because in Node class it null. You can assign it with default value and remove null check.
I suggest you to change a bit Node class, make it immutable:
private static class Node<V> {
private final AtomicReference<Node> next = new AtomicReference<>();
private final V data;
private final String threadName;
Node(V data1, String threadName) {
this.data = data1;
this.threadName = threadName;
}
}
And then you can simple go through all elements:
private final AtomicReference<Node> head = new AtomicReference<>();
#SuppressWarnings("unchecked")
public void offer(final V data) {
// create new Node
final Node<V> newNode = new Node<>(data, Thread.currentThread().getName());
// set root element if it's null
if (head.compareAndSet(null, newNode)) {
return;
}
// else pass trough all elements and try to set new
Node<V> pointer = head.get();
for (;;) {
if (pointer.next.compareAndSet(null, newNode)) {
break;
}
pointer = pointer.next.get();
}
}
And change print method:
#SuppressWarnings("unchecked")
public void printQueueData() {
AtomicReference<Node> pointer = head;
while (pointer.get() != null) {
System.out.println(pointer.get().data);
pointer = pointer.get().next;
}
}

how to terminate retrieval from a blocking queue

I have some code where i execute a several tasks using Executors and a Blocking Queue. The results have to be returned as an iterator because that is what the application that i work on expects. However, there is a 1:N relationship between the task and the results added to the queue, so i cannot use the ExecutorCompletionService. While calling hasNext(), i need to know when all the tasks have finished and added all the results to the queue, so that i can stop the retrieval of results from the queue. Note, that once items are put on the queue, another thread should be ready to consume (Executor.invokeAll(), blocks until all tasks have completed, which is not what i want, nor a timeout). This was my first attempt, i am using an AtomicInteger just to demonstrate the point even though it will not work. Could someone help me in undestanding how i can solve this issue?
public class ResultExecutor<T> implements Iterable<T> {
private BlockingQueue<T> queue;
private Executor executor;
private AtomicInteger count;
public ResultExecutor(Executor executor) {
this.queue = new LinkedBlockingQueue<T>();
this.executor = executor;
count = new AtomicInteger();
}
public void execute(ExecutorTask task) {
executor.execute(task);
}
public Iterator<T> iterator() {
return new MyIterator();
}
public class MyIterator implements Iterator<T> {
private T current;
public boolean hasNext() {
if (count.get() > 0 && current == null)
{
try {
current = queue.take();
count.decrementAndGet();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return current != null;
}
public T next() {
final T ret = current;
current = null;
return ret;
}
public void remove() {
throw new UnsupportedOperationException();
}
}
public class ExecutorTask implements Runnable{
private String name;
public ExecutorTask(String name) {
this.name = name;
}
private int random(int n)
{
return (int) Math.round(n * Math.random());
}
#SuppressWarnings("unchecked")
public void run() {
try {
int random = random(500);
Thread.sleep(random);
queue.put((T) (name + ":" + random + ":1"));
queue.put((T) (name + ":" + random + ":2"));
queue.put((T) (name + ":" + random + ":3"));
queue.put((T) (name + ":" + random + ":4"));
queue.put((T) (name + ":" + random + ":5"));
count.addAndGet(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
And the calling code looks like:
Executor e = Executors.newFixedThreadPool(2);
ResultExecutor<Result> resultExecutor = new ResultExecutor<Result>(e);
resultExecutor.execute(resultExecutor.new ExecutorTask("A"));
resultExecutor.execute(resultExecutor.new ExecutorTask("B"));
Iterator<Result> iter = resultExecutor.iterator();
while (iter.hasNext()) {
System.out.println(iter.next());
}
Use "poison" objects in the Queue to signal that a task will provide no more results.
class Client
{
public static void main(String... argv)
throws Exception
{
BlockingQueue<String> queue = new LinkedBlockingQueue<String>();
ExecutorService workers = Executors.newFixedThreadPool(2);
workers.execute(new ExecutorTask("A", queue));
workers.execute(new ExecutorTask("B", queue));
Iterator<String> results =
new QueueMarkersIterator<String>(queue, ExecutorTask.MARKER, 2);
while (results.hasNext())
System.out.println(results.next());
}
}
class QueueMarkersIterator<T>
implements Iterator<T>
{
private final BlockingQueue<? extends T> queue;
private final T marker;
private int count;
private T next;
QueueMarkersIterator(BlockingQueue<? extends T> queue, T marker, int count)
{
this.queue = queue;
this.marker = marker;
this.count = count;
this.next = marker;
}
public boolean hasNext()
{
if (next == marker)
next = nextImpl();
return (next != marker);
}
public T next()
{
if (next == marker)
next = nextImpl();
if (next == marker)
throw new NoSuchElementException();
T tmp = next;
next = marker;
return tmp;
}
/*
* Block until the status is known. Interrupting the current thread
* will cause iteration to cease prematurely, even if elements are
* subsequently queued.
*/
private T nextImpl()
{
while (count > 0) {
T o;
try {
o = queue.take();
}
catch (InterruptedException ex) {
count = 0;
Thread.currentThread().interrupt();
break;
}
if (o == marker) {
--count;
}
else {
return o;
}
}
return marker;
}
public void remove()
{
throw new UnsupportedOperationException();
}
}
class ExecutorTask
implements Runnable
{
static final String MARKER = new String();
private static final Random random = new Random();
private final String name;
private final BlockingQueue<String> results;
public ExecutorTask(String name, BlockingQueue<String> results)
{
this.name = name;
this.results = results;
}
public void run()
{
int random = ExecutorTask.random.nextInt(500);
try {
Thread.sleep(random);
}
catch (InterruptedException ignore) {
}
final int COUNT = 5;
for (int idx = 0; idx < COUNT; ++idx)
results.add(name + ':' + random + ':' + (idx + 1));
results.add(MARKER);
}
}
I believe a Future is what you're looking for. It allows you to associate asynchronous tasks with a result object, and query the status of that result. For each task you begin, keep a reference to its Future and use that to determine whether or not it has completed.
If I understand your problem correctly (which I'm not sure I do), you can prevent an infinite wait on an empty queue by using [BlockingQueue.poll][1] instead of take(). This lets you specify a timeout, after which time null will be returned if the queue is empty.
If you drop this straight into your hasNext implementation (with an appropriately short timeout), the logic will be correct. An empty queue will return false while a queue with
entities remaining will return true.
[1]: http://java.sun.com/javase/6/docs/api/java/util/concurrent/BlockingQueue.html#poll(long, java.util.concurrent.TimeUnit)
Here is an alternate solution that uses a non-blocking queue with wait/notify, AtomicInteger and a callback.
public class QueueExecutor implements CallbackInterface<String> {
public static final int NO_THREADS = 26;
private Object syncObject = new Object();
private AtomicInteger count;
Queue<String> queue = new LinkedList<String>();
public void execute() {
count = new AtomicInteger(NO_THREADS);
ExecutorService executor = Executors.newFixedThreadPool(NO_THREADS/2);
for(int i=0;i<NO_THREADS;i++)
executor.execute(new ExecutorTask<String>("" + (char) ('A'+i), queue, this));
Iterator<String> iter = new QueueIterator<String>(queue, count);
int count = 0;
while (iter.hasNext()) {
System.out.println(iter.next());
count++;
}
System.out.println("Handled " + count + " items");
}
public void callback(String result) {
System.out.println(result);
count.decrementAndGet();
synchronized (syncObject) {
syncObject.notify();
}
}
public class QueueIterator<T> implements Iterator<T> {
private Queue<T> queue;
private AtomicInteger count;
public QueueIterator(Queue<T> queue, AtomicInteger count) {
this.queue = queue;
this.count = count;
}
public boolean hasNext() {
while(true) {
synchronized (syncObject) {
if(queue.size() > 0)
return true;
if(count.get() == 0)
return false;
try {
syncObject.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public T next() {
synchronized (syncObject) {
if(hasNext())
return queue.remove();
else
return null;
}
}
public void remove() {
throw new UnsupportedOperationException();
}
}
class ExecutorTask<T> implements Runnable {
private String name;
private Queue<T> queue;
private CallbackInterface<T> callback;
public ExecutorTask(String name, Queue<T> queue,
CallbackInterface<T> callback) {
this.name = name;
this.queue = queue;
this.callback = callback;
}
#SuppressWarnings("unchecked")
public void run() {
try {
Thread.sleep(1000);
Random randomX = new Random();
for (int i = 0; i < 5; i++) {
synchronized (syncObject) {
Thread.sleep(randomX.nextInt(10)+1);
queue.add((T) (name + ":" + ":" + i));
syncObject.notify();
}
}
callback.callback((T) (name + ": Done"));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public interface CallbackInterface<T> {
void callback(T result);
}
And the calling code is simply:
QueueExecutor exec = new QueueExecutor();
exec.execute();
I am not sure I understand you, but why can't the worker threads put themselves Lists onto the Queue. You can then make a custom iterator that goes over the queue in an outer loop and through the subiterators. All without concurrency magic.

Categories

Resources