 Okay, my name is Alex. I'm in software development around 18 years, mostly C++ development, but also I have some experience with Go, just three maybe or maybe a bit more. So, and today I'm going to talk about mute access, deadlocks and one potential way to deal with this problem which could be quite useful in some cases. So, first of all, let's talk about why actually. First of all, so Go gives us cheap concurrency. That's nice, but at the same time, this feature I often overuse it a lot. And also, second thing is application could have state, not all application, but some of them. Let's say, let's say you have a manager and manager object manage some state for your application. And that's nice and good until you have cheap concurrency. Because with cheap concurrency, you have situation when multiple objects, multiple threats are trying to get access to your single object. And it leads us to something like this. You have structure with let's say two fields and you have mute access which protects your fields. Because you know about concurrent access, you know about deadlocks and that's acceptable with doing it from time to time but can we do better? Basically, is it possible to have structure like this instead of my original type in? So, how to achieve such situation and how can we use it? So, few words about motivation. Why we may want to have it? When we have mute access, when we are trying to protect our code from concurrent execution, we should think a lot about how big code block is. You should choose not too big from one side and not too small from other side. And definitely easy solution is let's lock whole function. It will work, but you don't have concurrency anymore. So, maybe you should lock on if blocks but that also have some issues. And that's not all because let's say you also need to choose when you should use read lock, when you should use read write lock. So, lot of things. And you should not forget to unlock your blocks because defer will help you a lot but defer works just on function scope. It will not work on let's say your for scope, if scope. So, in some cases you cannot unlock your mute access automatically you should do it by hand. In other languages you will have this problem basically but that's what we have in go. And why we care about as less as possible as possible function locks. Basically because of these add and low. That's quite theoretical part and actually we should care just about at least now one or one minus P where P is your code which could be executed in parallel because here let's say you have 25% of your code could be executed in parallel. It means if you will execute this code in 10 executors you will have speed up just a bit higher than three times. If you will execute on 100 executors you will have speed up even less than four time but it's still just theoretical limits because in real life you will have context switches you will have some events which are happening in your system and real speed up will be even slow. So that's actually my reason why we're trying to lock as less as possible but then still at the same time we should not have a deadlock. So that's quite complicated. And how basically application interacting concurrent world. There are two main approach. Yes, I know we have some others let's say we have so let's talk about this first. So main and most commonly used is hurt memory. I think everyone know about this way. You have actually my initial code was about this approach. Second approach is message passing. We have two main message passing ways or algorithm is communicating the question processes. This is go away or Actors model. In some languages they have Actors model by default. So let's have a brief talk about this model what they are, how they are used. As I mentioned, short model actually is available for almost every language. You can find few languages which doesn't allow you to have a short memory but still it's just a few. And it's fastest way because as soon as you have one object in memory you shouldn't copy anything. All your threat or even all your processes can't have access to single memory. So it's fastest. You cannot introduce anything faster than shared objects but everything have a price. And in case of shared object price is deadlocks, races and quite complicated code. So sometimes it's good. But CSP is your main way for communication in CSP model is a channel. At least in not in horror model description or papers but in real world how it was adopted. So now in go we have channel. The CSP is definitely much more secure than compare with shared memory. But it's still you can have a deadlock because one go routine may have multiple channel. So you still have a way to have a deadlock. Even channel is a mutics by itself by its nature. So you can have issue with this approach. Actors I think it bit less well-known way way for organizing concurrent communication but it's still very useful. The difference between CSP and channels are channel have identity. So sorry, actors have identity. Each actor can each actor have its unique ID and in CSP you just send in a message into channel and you read message into channel. In actor world you send message to actor and that's very important difference because your framework can organize message send for you so you will have less possibility for deadlocks. You will have less possibility for races but let's talk not about actors as it is because we don't have any cheap implementation actor for go. When I'm talking about cheap I mean let's say in Erlang we have actors out of the box. For go we have CSP out of the box and we should try to deal with it somehow. So what I'm talking about this is quite ancient pattern which was described in Posa book which is also really ancient 1996 but it still could be convenient in some cases. So what we have here we have one processor which works on on thread. We have multiple consumers which want something from processor and the only way to consumer and processor for talk to each other is a Q message queue. We have message queue requests. This message queue actually when consumer want something from processor it will send a message. This message will be added into request message queue. Processor is synchronous. So it works on on thread but it execute requests one by one. So as soon as first request process it it get next from queue do something with it and send response to consumer. And now I'm going to talk how to organize these such model and how it could help us. So first of all, now it will be more practical not theoretical but so first of all how to talk, how to tell our processor what we want. Let's imagine our processor will do just two thing. It will generate random number. The random number generation is an example of fast command command which could be executed very fast. And let's say we will have one slow command run cmd means run something, run some command from your operation system. Let's say we can ask processor to run a less or a call. So something from your environment. And this example second run cmd is a slow command. So and idea is slow command shouldn't affect fast command anyhow. They should be absolutely independent. So let's see our request structure. Request is actually this part what we send to our processor. Oops. So request will have three fields. Command ID because we should tell our processor what we're expecting from him. Interface with data. Unfortunately, we should use interface because of no metaprogramming. And output channel. So this channel is basically way to send message back to our consumers. And that's what we will send back. We will send something because we have general solution. So we can send absolutely anything. And again, interface the only way to do it. I don't like it, but that's the only way. Event loop. Our processor should have event loop. It means we should read message and decide what to do with this message. Also, we should have a way to terminate our event loop. That's why we have two channel. One channel with just a flag, nothing more. And one channel with requests. Where request is this structure. So that's our request. What we actually ask in our event loop to process for us and give a result for us. So we'll read our request and call some function for the processing. How we will process requests. As I mentioned before, we have two types of requests. One fast and one slow. Fast request, we can process just in place. Let's say like here. That's definitely for just illustration purposes. But the idea is if your request is fast, let's just process it on place. It could be something different. Let's say you are caching your data. In this case, you can search for data in your cache right in place and send it back. Or if you have slower request, like do something which will take long time, you should never block your execution thread. So it means you should move this task out of your main loop. Guarantine is very good way to do it. So we will execute it in extra thread. And this is our long request. Let's say, so first of all, this loop is just for illustration purposes, nothing more because actually command line, command also could execute it quite fast. And for illustration, I was thinking about real delay. So they sleep nothing more than illustration. And here we have request. As you remember, we use interface, so we need to convert our request to some data. We execute in our request and send in response to output channel. Let me return back. So this is our command data. This our response channel. And this request was, we choose this case based on that request. That's how we are dealing with long commands. And just a few final actually things like how we should initialize this. First of all, we need done channel. It could be blocked channel. We don't care how many elements we store in our channel. It should be input channel. Input channel should have some buffer. How big this buffer it's up to you, but sorry, but size should reasonably big. I mean, we should not block our executors or our collars just because we don't have room in our channel. So 10, 20, it depends on your workflow. And last step, let's just execute our event loop. And that's basically this. So this processor works in owner, in owner GORO team, in on thread. And so that's why we executing it in on thread. And as I mentioned before, input channel is the only way to tell our processor to do something. So we want to call, let's say, a less command. Okay, we should send such request. We mentioned in here that that's run simd. This is our command and response. So even we have one second delay. If something else, some other request, let's say, gen number will be called before this response is ready. It will be processed first. Because we are moving long command into owner GORO teams. You may think, how can I reuse asynchronously calculated data results, let's say, you want to cache your executed command results. So you should pass, in this case, you should pass them into a processor somehow. And that's actually the only way and that's works very well. So you can send it as a message back. You need to, in this case, you need just introduce new command run simd result. And this command will be processed as any other command in your main execution cycle inside processor. Yeah, I think quite fast, but anyway, that's all for now. So if you have questions, welcome. Why did you decide to pass the request object as opposed to passing a pointer to the request object? Any thoughts about that? You mean, here. You generate a new request for your reply response. Why don't you just, you pass a reference to the original response. You mean, you know, this, that's just her illustration. No reason for this at all. That was, you know, my idea was to create as simple as possible. And that's why, let's say, I didn't include these into. Point the. Yeah, so no reason for this. Our code. I like this method because it's something which I actually put into practice. That's very useful because in our code base we had a lot, okay, not lot of, but multiple manager objects which had lots of new taxes because we were trying to lock as small piece of our code as possible. And, you know, it really was a nightmare in terms of support. When we switched into this way, I wouldn't say it's lower because basically here you're also locking just minimum part of your code base and mutex, sorry, and channel is kind of your mutex and communication ways in same time. So in terms of speed, it works more or less same and maybe even faster because just because you not always can choose right scope for locking. So this may work faster, may work slower. It depends on how good you are in playing with mutexes. But it definitely safer because you have zero mutexes. You have zero way to have a deadlock here because main code which is in processor single threaded. That's all.