sobota, 25 lutego 2012

Everything You Always Wanted to Know About Fibers * But Were Afraid to Ask

I recently found out that most Ruby developers that are not familiar with Event Machine usually knows very little about fibers. So I decided to share some very trivial examples that will help you understand them quickly. Each example should push your knowledge and imagination about possible usages a little further. Tell me in comments if that worked for you.

Example 1:


What happened here, you may ask. We created new fiber. At the begining it was stopped. So we resumed it. It executed and ended. Trying to resume it again did not work. Actually that was very similar to Proc except that we can always call Proc multiple times.

Example 2:


While being inside fiber we can yield. That means that fiber stops and control goes back to the line that resumed fiber. We resumed the fiber second time starting from where we escaped (yielded) previously.

Example 3:


As you see, you can return value from fiber when yielding out. That value is accessible to code that called fiber (resumed it). How is it accessible ? Well "resume" simply returns what was yielded from resumed fiber.

Example 4:


Nothing extrordinary here. When fiber is fininshed the value of last statment is returned. Like always in ruby.

Example 5:


Just using iterators inside fibers to yield multiple times and outside to resume fibers multiple times to get you comfortable with doing that :).

Example 6:


This fiber can be resumed infinite number of times. It returns randomly generated value to the caller.

Example 7:


As you can see in this example now you can resume the fibers in whatever order you like. You are in charge of execution order. That's usually the point of using fibers.

Example 8:


Why would you ever want to use fibers ? Well consider this example. You have an algorithm that requires to somehow operate on the result of two different kind of operations. One of them is very tough like computing prime numbers (obviously my solution to that could be way more optimized, but that is not the point). The second kind of operation is very easy (computing square in our example, trivial). You could use threads for doing that but threads are scheduled by Ruby VM or operating system and you have almost no power over them. If we computed prime numbers in one thread and squared numbers in another thread then probably we would end up with lot more squared numbers computed than prime numbers.

In our examples every algorithm is given enough time to compute one, next sequence number and then yields. That gives the scheduler (our code) the opportunity to resume another fiber and compute next number of second sequence . Fibers give you the ability to manually schedule peaces of codes and avoid the need to synchronize threads for accessing shared data structures.

Example 9:


Two fibers can use the same shared variable without using any kind of lock for accessing it because we are guaranteed that only 1 fiber is executing at the same time. So every fiber can be sure that no other fiber changed anything in any variables until Fiber.yield is called next time.

Example 10:


If fibers are your jobs then you need to know if they are unfinished and can be resumed again or finished and calling them again would lead to "dead fiber error". Usually this is not known upfront by the scheduling code but rather recognized based on returned value.

Summary:

I hope that now you know something, something about fibers (at least more than before reading this post) and it is not a black magic anymore. They might not be of any use to you but that is definitely not an excuse for ignoring them completely.

Caveats: