This workshop will be retired on May 31, 2020.
Readers-Writers Problem12:49 with Amit Bijlani
Multiple threads reading at the same time while there should be only one thread writing. The solution to the problem is a readers-writers lock which allows concurrent read-only access and an exclusive write access.
Race Condition A race condition occurs when two or more threads can access shared data and they try to change it at the same time.
Deadlock A deadlock occurs when two or sometimes more tasks wait for the other to finish, and neither ever does.
Readers-Writers problem Multiple threads reading at the same time while there should be only one thread writing.
Readers-writer lock Such a lock allows concurrent read-only access to the shared resource while write operations require exclusive access.
Dispatch Barrier Block Dispatch barrier blocks create a serial-style bottleneck when working with concurrent queues.
Our image downloading code is not very efficient. 0:00 Each time the table view so is displayed it goes out to the network and 0:03 fetches the image. 0:07 We need to make it a bit more efficient by caching the image. 0:09 If the image exists in the cache then display it, 0:12 if not go fetch it from the network. 0:16 Now, one important caveat here, 0:18 this image caching functionality that I'm showing you is very rudimentary. 0:20 It's actually caching the images in memory. 0:25 You do not ever want to ship code that is saving images in memory, 0:29 especially every image that we provided. 0:34 This is simply to demonstrate the readers-writers problem. 0:38 All right, so I've created this class, it's called the image cache. 0:41 And it's a singleton, so you can access only a single copy of 0:45 this class using the shared static variable. 0:50 It has a private mutable variable called image cache which is a dictionary. 0:54 It uses to String as a key and UIImage as the value. 0:59 So when you say add, it adds that image using the file name or 1:05 the image name as the key, and the image as the value. 1:10 And when you see get, you provided the image name, 1:15 and it returns you the image if there is one. 1:19 When you save remove all, it basically just resets the dictionary. 1:23 Okay, so let's go back to our image table view so, and use our image caching. 1:27 So when we actually receive the image data and recreate this image. 1:35 What we're going to do is going to call ImageCache. 1:40 So ImageCache.add, oops, .shared.add. 1:43 And we're going to use the image URL, and we will pass at the image. 1:50 Great, so we have cached the image. 1:56 Now in our configure method, oops, there's an error here. 1:59 We need to implicitly on wrap it because when we initialize UIImage, 2:05 it returns back and optional, right. 2:11 Going back to configure methods. 2:14 We will first check to see if our image is cache. 2:17 So we'll say ImageCache.shared.get, and 2:22 here we're going to use photo.imageURL. 2:26 So if we have the image, 2:33 then simply set it, wait to set up image. 2:37 Else, we're going to say loadImage which means that you have to download it 2:43 from the network. 2:48 And then and only then can you set it to the image for you. 2:49 Now to demonstrate that this is actually 2:54 working, we'll just print, 2:59 cached image loaded, so is that simple. 3:03 Now, let's run or app. 3:08 Right, let's open up the console. 3:12 If I go back in the other direction, it says, cached image loaded. 3:19 Great. 3:24 Now there is a slight problem with this implementation. 3:29 The biggest problem with the simplistic implementation is that we're 3:33 now using concurrent programming. 3:37 And there'll be multiple threads trying to access our image cache. 3:40 Basically, when we created this loadImage, 3:44 we're accessing the global queue which is a concurrent queue and 3:48 we're asynchronously downloading images. 3:54 So each time we download image, it's accessing the ImageCache. 3:58 And they could be concurrent threads occurring at the same 4:04 time trying to add images to our ImageCache. 4:09 So what happens? 4:12 Well there are several problems that can occur by doing that. 4:14 So let's look at some of the issues that can occur. 4:18 A race condition occurs when two or more threads access shared data, and 4:23 they try to change it at the same time. 4:27 And in our case the shared data, our shared resource is our ImageCache object. 4:30 A deadlock occurs when two or more tasks wait for 4:37 the other to finish and neither ever does. 4:40 So what we have here is the readers-writers problem we have multiple 4:43 readers and multiple writers. 4:47 Multiple threads reading at the same time while there should only be one 4:49 thread writing. 4:54 And the solution to this problem is the reader's writer's block. 4:55 Such a lock allows concurrent read only access to the shared 5:00 resource while other operations require exclusive access. 5:04 This patch barrier blocks create a serial style 5:09 bottleneck when working with concurrent queues. 5:12 Once again, we have an example off a concurrent queue. 5:15 Except in this example, we have a barrier block in the middle, displayed in red. 5:19 Once the thread starts dequeuing blocks, the red barrier block will not 5:26 execute until all the existing work on the queue has finished running. 5:31 Then while the barrier block is running nothing else can run. 5:35 Once it has finished, the normal concurrent execution can continue. 5:40 To implement a barrier block in our ImageCache class, 5:45 we first need to implement a custom dispatch queue. 5:50 So going over to the ImageCache class and will create another private variable. 5:54 And we'll simply call this queue, And we are going to provide it a String. 6:01 So in this case, we'll say 6:10 com.teamtreehouse.marsrover.imagecache. 6:14 So why a custom dispatch queue? 6:20 Well you see in the image view cell we simply use the global concurrent queue. 6:23 But barrier blocks cannot be used on a global concurrent queue, 6:27 because they will end up blocking other tasks, 6:31 which will have unintended consequences within our app. 6:34 A custom concurrent queue as seen in the animation, will allow for 6:37 multiple queues to read our cache. 6:42 And only allow one queue to write to it, thus providing exclusive write access. 6:45 So it's good to use reverse domain here, so 6:52 that we know that this queue is unique to our application alone. 6:57 I added ImageCache, so that if we wanted to create multiple custom queues, 7:03 we could do so by appending to the reverse domain. 7:09 So first, 7:14 let's implement the read functionality which means when we're getting the image. 7:15 So this is simple, this is a simple read, and we just need to provide read access. 7:20 So here will use async method. 7:27 So right here, where we're accessing data from our dictionary, 7:30 which is our shared resource. 7:34 We'll simply say queue, remember, 7:37 it's very similar to the dispatch queue because queue is an instance of dispatch. 7:39 You accept this is our custom queue. 7:44 Now here is an issue. 7:52 Firstly, we cannot use it as an async method. 7:54 We want to return this image right here. 7:58 We want to return it as part of this get function, so 8:01 we're going to use a sync method, which is a synchronous method. 8:06 So until we don't access the image from our dictionary, 8:13 this line is not going to move to the next line of execution. 8:18 So once we have our image, 8:23 then we can return it as part of this function of this method. 8:25 Great, so we have implemented the reader part of this equation. 8:29 Now we need to implement the writer part which is where we're 8:34 adding to our ImageCache. 8:38 So for this, we need to create a dispatch work item. 8:40 So I'm gonna create a constant, and 8:46 I call this workItem = Dispatch. 8:50 Now, the DispatchWorkItem takes two parameters. 8:54 The first one being the quality of service, 9:00 and the second one being that type of dispatch work item. 9:05 So let's take a look at this and look at the class reference. 9:12 So the quality of service, 9:18 the DispatchQoS are basically the different priority levels. 9:21 Previous to this, we had priorities, and now those priorities kind of 9:27 map to the quality of service, pretty much kind of the same thing. 9:31 These are enums, and as you can see, you have various enums that say user 9:37 interactive, user initiated, default, utility, background unspecified. 9:42 So in our case, we want the default quality of service. 9:48 And as far as the dispatch work items, I'm just going to Cmd+click here. 9:54 And you have barrier which is what we need to, we want to create a barrier block. 10:00 You have detached assignCurrentContext, noQoS, and few other values. 10:06 So in our case we're going to do .barrier, oops. 10:14 And finally, 10:23 we provide our block of code which in our case is writing to the dictionary. 10:24 Now, just so that you understand the quality 10:29 of service high is userinitiated. 10:35 The default is of course Default and Low is utility, 10:40 and even lower than utility is the background, right? 10:47 So this kinda like your high, medium, low. 10:54 And we're getting a warning because we've declared the work item, but 10:59 we haven't really used it anywhere. 11:03 But we have to use our queue our custom queue, and we have to call it. 11:06 So here we're going to a synchronously execute this dispatch work item. 11:11 So we're going to use a method called execute. 11:17 So why asynchronous? 11:30 We misspelled default here. 11:32 So why asynchronously here and synchronously here? 11:35 Well, when we're reading, 11:39 we want to make sure that we access the image from our dictionary. 11:40 When we're writing, we don't care when it's actually doing the writing. 11:45 We just want to make sure that once it's writing, 11:49 it's blocking any other reads or any other writes from happening. 11:52 Okay, so let's run our application again. 11:57 Now once we run our app, 12:01 we should notice a difference in the performance. 12:04 Of course, is a very crude implementation because storing so 12:10 many images in memory will only increase our memory usage. 12:14 Ideally, we want to persist these images to disk or use something like NS Cache. 12:17 But by implementing a barrier block within our ImageCache class, 12:23 well we made our class thread safe. 12:28 A thread safe class can guarantee its state even when accessed from 12:30 multiple threads. 12:35 Now you don't have to worry about data corruption. 12:36 The reader-writer schemes are fundamental building blocks for 12:39 sharing resources when dealing with concurrent programming. 12:42 Check out the teacher's notes for further reading. 12:46
You need to sign up for Treehouse in order to download course files.Sign up