 Yeah, thanks everybody. It's actually a pleasure to be able to present this here because literally the concepts of Threadweaver have been developed when Qt was not yet able to do anything that it tried to do. So the first proof of concept of what we were trying to build with Threadweaver came up at the time of Qt 3 when reference counting for example was not atomic and all the relatively simple and easy to use concepts that we wanted to have practically impossible without writing a new Qt which then happened with Qt 4. And then that's why it has this long history. You're looking probably at the fourth version of Threadweaver. The concepts have stayed the same and then the implementation has been redone and redone. Then in Qt 4 we needed quite a number of small helpers and tips and tricks to make things work and with the current Qt the code is very straightforward. So yeah, it's really interesting to look at it today. What's the presentation about? We are going to have two main parts. One will be just introducing the basic concepts of what Threadweaver is built off, if you will. And then the second part, pretty equally distributed in terms of time, will be, well Qt things you can do in Threadweaver, things that other ThreadPool-like concepts in Qt applications don't necessarily have. In case you're wondering who that guy is. Yeah, I've been programming with Qt and KDE since 97. I was on the KDE board for a couple of years. I worked at KDAB, learned more about Qt, so I'm teaching Qt a lot. Today, the focus of my work is less technical. My two main free software contributions today are working at the Open Invention Network, where we're trying to build a patent pool of cross-licensing that protects Linux and other free software. Very successful 2,000 member companies and the sponsor of this conference, by the way. And then I'm working with the FSFE, I'm part of the German team and I'm in the General Assembly. And that's that. Also, yeah, and I teach the stuff. So in case you live in Berlin and you want to take university course on open source intellectual property, you can take that here. What is Threadweaver? It's not a ThreadPool. Well, the kind of it is. You can do everything with Threadweaver that you could do with a ThreadPool, but you can do a lot of more things with ThreadWeaver that you can't do with a ThreadPool. We call the Concurrent Execution Scheduler. It is written for Qt and KDE programs. It's written for Qt because that's its only dependency and it's written for KDE programs because it's a KDE framework. So you get ThreadWeaver by installing a KDE framework into your application. Built with CMake. The only thing that you need extra from CMake and Qt are the KDE extra CMake modules that are used to configure it. And once you've done that, it's very simple to integrate ThreadWeaver into your applications. And all the code I'm going to show you today or I'm going to reference is in the original ThreadWeaver repository. It's based either on the unit chest or the examples that are in the repository. And there you can also see kind of the three lines of built system code that you need to add to your project to, for example, use ThreadWeaver in a CMake project. I'm assuming that if you do production code then you don't use QMake. So what's the focus of ThreadWeaver? First, it provides high level functionality for concurrent execution. High level in that it's not necessarily concerned with like something as small as a runnable. Like, of course there's a basic unit of execution but we're looking at a higher level. For example, we're saying I have this set of things that needs to get done. And before this gets done, some setup needs to be done. When that's all done, some tear down needs to be done. And this is something you can model in ThreadWeaver which I think is, if you think about it, probably matches the problem that you're trying to solve more than saying, I want to apply this algorithm in a parallel way. So this is also the differentiation from, for example, from something Qt concurrent. So ThreadWeaver was written with the work of an application developer in mind whereas Qt concurrent was written by computer science PhDs, for computer science PhDs. ThreadWeaver is modular in a way that all the concepts that ThreadWeaver is applying kind of extend each other, not necessarily building on each other. You will see that. So there are Qs and jobs and the policies, et cetera, that you can understand each of these concepts separately and use them. And I call it conceptual usability. So I want this to be relatively easy to use and intuitive as well. And if you're interested in seeing kind of what it can do, the tests are actually a very good way to learn about it because the tests use all this kind of conceptual usability a lot. So you can create a local pool as a local variable and you can leave that scope and the pool goes away, et cetera. So things like that are, I think, pretty powerful concepts for parallel execution that, for example, QthreadPool itself doesn't have. I don't want to bash QthreadPool. It's a very good thing and a very good tool and it's built into Qt, but it solves a very specific problem and it's very limited, right? There's one pool, you don't understand yet another pool, et cetera. And this seems to be like you need a little more time sometimes and then you can use ThreadWeaver. So in a way, if you've ever looked into what Apple built with Grand Central, I think we built what they wanted to do and didn't finish. Let's talk about basics. The absolute essential thing that you need to know about in ThreadWeaver is a job. Of course, that's your runnable, right? If you want to implement something that does something, you need a job. We call it a basic unit of queuing and execution and it works much like a runnable. So you create it, you can either, sometimes you can provide payload, for example, there's a way to use a standard Lambda function and wrap that into a job and throw it away. So and then it just is executed. So that's basically intuitive. You already know how a runnable works. So that's, think of a job like a runnable. However, memory management. The, one of the issues that you probably have seen is what the question of when is a job obsolete? Like when is the runnable done and you don't need it anymore? ThreadWeaver solves this problem by queuing SharePointers. So you're creating a SharePointer to a job and you're handing this over to ThreadWeaver. If you keep a reference to that, then the reference count is one, right? And then two and you pass it into a queue. So this thing stays there. If you don't keep one, it's a fire and forget job. You queue it, you don't keep a reference and it gets destroyed immediately after it's finished. And you don't have to worry about this. That and alone, I think, if you look at the code and the management of the payload, simplifies a lot of solutions that I've seen that use this. Jobs are also a little more, they're not meant to be absolutely minimal like runnable, like one virtual function, no local variables because we're assuming that you do something with a job that is like substantial work. So it obitrates the cost of creating it as opposed to like the intention to create millions of them. So it has things like success status. It has a way to like a mechanism to request cancellation. You know that cancellation is a very difficult issue. You can't just go and delete the job. It might be in progress. You can call it and say, stop what you're doing because there's no way to do that and C++ we don't have interrupts or something that would implement that. So all you can do is ask the job nicely to cancel and there's a mechanism for that. And jobs have priorities. Priorities here are not priorities for the threading scheduler, like an operating system. They're priorities for scheduling in the queue. So you can add jobs to the queue and you say there's a higher priority, there's lower priority and they will just end up, the queue's a sorted queue if you will and the highest level priority jobs will be executed first. That there's no order here and in terms of priorities, there's no guarantee of order of execution because the higher priority job can still be done like take longer time and another one can be picked up by another thread, right? So if you look at order of execution there's a different mechanism for that. This picture here shows somebody doing a job and then two other people looking at it. This is also a metaphor for the decorators that we have, right? So you will see later how we can wrap jobs into other jobs and therefore this way change the behavior. Job is a very central concept of the threat viewer in that a lot of the things that we talk about later are actually jobs, which means there's exactly one way to queue something in like to enqueue some work. That's one method says enqueue this job, not the job, a vector of jobs. Why is it a vector? Because one of the standard issues that you have with using thread pools is atomic queuing of work. You always have this like if you write a for loop and you queue things up, then of course the first things get already done while you're still queuing and that's hard to manage sometimes. In front of you what you do is you create a vector, you put all your jobs in there and then you have this one method we say do this please and then yeah then that happens. So because so much is based on job the API of the queue is super simple. There's one thing, you can put a vector of jobs in there. And I talked about queues, so your queue is the threat pool, sort of. Similar to queue threat pool, there is a global instance. So when you have a queue application object and you link threat viewer, then you also have a global instance. However, you can create a local variable of type queue and say please have eight work threats and that will be a local queue. There's functionality in the queue class to suspend execution. That means you call a function that says all the working threads should finish what they do but they should not pick up new work and then after a couple of minutes, seconds, whatever how long your job takes, job's take, the queue will send a signal I am now suspended. That means it will not start new work. Sometimes that's very useful. So this is less, this is what I meant with conceptual usability. This is less something related to parallel execution but more related to managing the workload in your application. So it's like a suspended queue is a wonderful thing to work with because you can put up, you can set up a complex structure of things that it should do and then you tell it to do it. So that's sometimes easier than to use lots of, for example, features to make sure you wait at a specific point before something happens. And then of course you can resume it and there's a function to finish what is in the queue, which is blockings. This for example, something you can use before you exit your application. One thing I have not on the slides is the number of worker threads is dynamic. That means you can set the target number at any time and it will adjust it. So if you have eight worker threads running and they're doing something and you say the target is now six, then two of them will, as soon as they finish what they're currently doing, they will exit. And I will have a demo for that a little later. So we talked about jobs and queues. Now we talk about a very central concept to Fred River and that's aggregation of jobs. So the essential class here is called collection and that's, as the name says, a collection of jobs. So you create a collection and you put 15 jobs in it and that's now one unit. They belong together. Interestingly, the collection class is a job itself so you queue it as you queue a job. So that's why we only have one method to queue. So now you have a way to say I want all these things to be done together. And for example, decorate this with a queue object and send me a signal when all this is done. So you see that the concepts in Fred River are made for people that are, for example, worried about managing the use interfaces as well. Where you say I have all these things that need to be done, I need to my use interface to update when that's done. I don't want to implement code that counts how many things are done or et cetera because I can formulate with a Fred River collection that this should all be done and when that's done, please tell me. Since it's a job, the collection is a job, it has a run method and the run method of the job itself, of the collection itself was executed first. It actually works to create payload, like elements of the collection in the run method. And I have an example for that later. There's a special version of collection that's called sequence and it does exactly what sequence would do. So you put jobs in there and they're executed in the order in which you add them to do a sequence. Now, question? No, okay. You can aggregate these things. So a collection or a sequence is a job, right? So there's nothing that speaks against using a sequence to create and to add collections into that sequence and add elements into that collection, in these collections. And then you can draw a graph of how this should be executed and you can trust that we were to do that for you and it will happen. A very common way to use this is to use a sequence for your, like we have three steps to do, setup, main work, processing of the results. And then since you're adding the main work as one collection, you can be sure that the third step only gets executed when all the other steps have been done in parallel as fast as the pool can do it, but only when they're done. I think of something like rendering a web page where say I'm pausing the original text, it contains lots of external references and I have a collection that downloads all these external references and now I'm waiting for this collection to be finished and then I render the page. So this is the kind of level of abstraction that you should think of. Don't think of programming the run method. This is a little too low level. Policies are your way to impose your will on the queue or multiple queues because policies work even if you assign them to multiple queues at the same time. So for example, there's already built in something called a resource restriction policy. You can define any resource, which is a counter. So for example, you say I would like to have this many jobs that access the network running at the same time at most. Then you create a resource restriction that says this is my network job resource restriction and you assign it to the jobs. If you do that and this thing says maximum four at a time, you can queue as many network requests as you like into the queue. The queue will only process four at a time. This is very interesting because one thing that I see repeatedly is that people use the work account of the pool to manage payload or workload. How much should be done at the same time? This is the absolute worst way to do it. What you want is a pool with a lot of workers and you want to use policies to say what the workers should be able to do or not. Because that means, for example, that you have one pool and if you have four network jobs working, you still have 12 threads waiting and the other things will just bypass. That works much better than using the size of the work account to manage something. Interestingly, dependencies as in we talked about sequence where things are done in a certain order. You can also declare this job depends on that one and then the second one will only be executed when the first one finished. This is also a queue policy, so it is kind of a modular concept here as well. So internally, that really uses these policies as well. I will have an example later where you can see that you can use queue policies to manage overall load. So you implement something that reads the system load and then adapts the number of, say, CPU intensive jobs using a resource restriction. So system load goes up, that goes down and then you manage. That's the overall system. Yeah, question? It's CPU, heavily using the CPU. Is that also a resource that you can say? This job is just logging your CPU? Yeah, so basically what you do is you create a... Can you read the question? Yeah, so the question was, how can CPU, is that a resource? The answer is yes, but you have to program that. So you say, here's a resource restriction. This is my CPU intensive resource restriction. I assign this to, like I have a job class that does image calculations and in their constructor, they all assign this policy. So it's like, set it up, two lines of code. And now you can say, whenever you queue one of these jobs, the policy will make sure that only say eight of them will be processed at the same time. This is a very good question because that's why you want to pay your pool than that because you don't only do CPU intensive things. You want your pool to be bypassing the CPU intensive work with network operations all the time. So that's why you want a big pool and the resource restrictions to manage what should be limited. Good question. Decorators, if you read your book about design patterns, then you understand decorators. They're like the Matroshka puppets. So you wrap one job into another, into another. And we use this to change all kinds of properties of jobs. So for example, if you want to adapt the priority a job has and you don't have, you're not creating the job. Somebody else created it. You can change your pool to say everything that comes in is down prioritized immediately. I have an example for that. If you want to add logging to the run methods of all jobs, you don't change the run methods of all jobs. You decorate them with a logger. Then you have a log message before the payload gets called and log message after or you want to collect time sequences, time series with Prometheus, then you add a Prometheus data generator as a decorator. Here's an example of that in a minute. For, to make this all usable and you don't have to write a gazillion lines of code, there's a stream like API like IO stream like API where you can say, here's a sequence and you have stream operators saying, job, job, job, job, job, job, job. That reduces clutter I guarantee by this many percent. You will see some examples of that. One interesting aspect here is if you do that, there's something called the stream. So the thing that you stream into is action object and you create that or you reference that. Everything that you do in this set of streaming operations all the way to your finishing semi colon will be queued in an atomic operation from the queues perspective. That means again, the queue does not start to process this while you're still queuing, which is helpful because sometimes otherwise you already done half way but then this instruction returns. So that was a lot of basic and introduction. Let's look at some code. This is the streaming API. And this is from code that is in the factory repository. So you create a sequence and then you, for example, use streaming operators to add elements to the sequence. Make job is a function that creates a SharePoint or two job and this takes a lambda function. So, and this is a member function or calls a member function. The lambda function that calls a member function. So this way you can basically say in this sequence from the pool call these three functions. So in this order, three lines of code. Try that with thread pool. And then you see last line is this, put this into the stream. Understood? The other make jobs, make job variants where you don't have to use lambdas. So it's supposedly rather simple. The pattern of calling back to an object that is in the main thread and calling member functions from the pool is quite common. It's helpful to not have to copy lots of member variables. Of course, then you have to make sure your member functions actually get called from a different thread. But typically, if you can synchronize that, well, it's a good approach. Because then you can create an object. Oh, it is also good for testing because you have an object where you call the functions and if you write the functions re-entrant and then thread save, then you can write the test but then using them, you can call them from a different thread. So that's why it's good to keep all the logic in one object, the payload object. Cool, here's some decorator examples. So this is the priority decorator, the example that I gave where you get jobs and you want to adjust their priority without calling their functions. So you have a job and you wrap it in a priority decorator, classes up there. These all will have priority too. And the concept of decorators is that you wrap one object into another object into another object. They also delete what they hold. There's a constructor parameter of the decorator if you don't want that. So the idea is that in one line of code you can create three objects and the result is what you'd queue as a job. And then this is, for example, a self-made one that one you could implement that adopts the priorities. That's a simple example, let's look at this one. Queue object decorator, that's actually built in. And it's very easy to use. Jobs in that threat reviewer are not queue objects. You cannot send a signal I'm done from the job itself because that would be a lot of overhead. But all you have to do is you, when you create your sequence, right, you meet the weapon in the queue object decorator and now you have a done signal. It's really simple. It's so simple that people look that it didn't understand it and ask me to write a blog post about it. But this, three lines of code. All right, just keep in mind that with this you have to make sure that auto-relate is declared properly because the outside decorator assumes that it owns the payload. Here's one interesting one. We have this issue that you have a global instance and the global instance is, what if you want to wrap the global instance into something or change its behavior? It's created before you access it. It's in a single. So there's a factory that you can register and with that you can register an object that will create your global queue. Generally the queues are, you can subclass the queue and change what it does. For example, job counting weaver is a queue that counts how many jobs have been processed. You can look at the code. It's in the factory repository. Or if you want to add logging to the global queue, that's how you would do it. You would intercept when the queue is created and then you implement something that whenever the queue performs a job would log and then that's how you add logging without changing the code all over the place. That's a very modular thing to do. And then you have two lines of code. You set up a global queue factory and then you create your queue core application object. Here's an example of these that we call the generator collections. So a collection that when you execute generates its own payload. You see this is the run method of the collection itself. And this is of course a stupid example. It does something with the sequence element by element. So it adds jobs to itself. You see that this new job something. And that means when the collection itself is done, its elements are what you just queued. Now this is of course a very simple example. I use this to implement HTTP download requests that are win load. You know these where you request the resource and then the response is they are 1000. I'm giving you the first 20. And you do that in a run method. And then you write a for loop that says and now with the window size I know I'm creating 19 other jobs that should download all the other elements. And instead of now waiting and doing this in loop I queued this as my collection elements which means they can all be done in parallel and I'm done. And so this way you can actually model things like I don't really know how many elements I need to execute before my first request is done. You combine that with a resource restriction for network requests and you have modeled your download manager in a couple of lines of code. Load management, this is an example. So I will show it in real life in a second. Load management means we want to, for example, in some way dynamically react to the system state. So let me give a little demo here. So imagine we have an application that scales images. So up to 16 workers. Now we say manage system load automatically. Yeah, let me just add some files to it. So either the display quality is a bit difficult, not too good here, but now watch it. It will start processing these images to generate thumbnails. 13 workers. So basically what now happens is because we are doing something, system load goes up then the code I just showed you uses the standard Unix get average, I think a system call. Calculates an ideal number of workers but based on that it emits it as a signal. And we use that to adapt the maximum number of threats in a queue or in a queue policy for CPU intensive threats jobs. Now you see how it goes down to 11. So I said in the beginning that a lot of the things that ThreatMeverers are supposed to be modular and composable and orthogonal and individually usable. And here you see how things then start to come together. And I don't think this is something you can easily do with queue ThreatPool. So, and this is the code that you just saw in action except that where they say it's magic that computes target work account, work account there is actual code that does it. And we have one minute left and I'm done. So this was really just an introduction. ThreatMeverers easy to understand if you use it. Basically everything that you tried to do is supposed to work as you imagine it should work. If it doesn't, it's a bug report. So send me a unit test that fails and I'll fix it. Enjoy it. And if you have any questions I think we have a minute or something to answer them. No questions whatsoever. Any questions? There's one. In the example you just showed you had 11 workers. I was wondering how many cores do you have? Why do you think it comes to the number 11? I actually had a top maximum of 16. That's just a restriction that is set in the code. This is an I7 quad cores or eight virtual cores. The general assumption is in queue ThreatPool I think the general recommendation is like use as many workers as they have CPUs plus one or something. With ThreatMever I would generally recommend to have a bigger pool. So 16 or 32 is actually good because you see these four numbers, file orders, image loaders, et cetera. These are resource restrictions. So the example implements saying I only want four objects to write to the disk at the same time or to scale the images. So the assumption here is that image scaling is CPU intensive, file writing is IO intensive. So you manage what happens at the same time using resource restrictions and use a large pool. This also shows of course how you can, the image scalers are limited because you don't want to hog all the CPUs or you want to reduce them if the system load goes up but then the file writers can bypass them because you didn't limit the number of worker threads you limit it how many CPU intensive things can run. So does that answer the question? By the way, you can see now that system load goes down and the recommended work account goes up again. Hi, at the beginning you've mentioned that how can you tell what our job is finished and you said that you're gonna be using SharePointers to do that. But after that I saw you adding some jobs into the queue using operator new. So how does that work? Does it get robbed in a SharePointer inside and how does it work? Yes. No. There's one assumption here and that is if you give a raw pointer, not a SharePointer, you are managing it. Which is an interesting idea because for example you can create jobs as local like stack variables in your code and as a member variable for example of an object. Then you can queue this job and then you don't want it to be auto-deleted and so you don't want to create a SharePointer that doesn't destroy it. So you queue it as a non-SharedPointer and then Fatweaver assumes that you're managing it. So that's something to know actually. Yeah, I have a question. Okay, so thank you very much Merko. Thanks everybody.