5

I'm using server.socket to stream data to multiple clients, server.socket uses Threads for each client connection. I currently have something like this:

(def clients (atom ())) ; connected clients defined globally for that namespace (swap! clients conj a) ; adds a client (which is an atom itself as well), this is in a function that is run on the client's thread ;I want to better the process of removing a client! (dosync (reset! clients (remove #{a} @clients))) ; removes client from list, run in a function on the client's thread 

I run a function which runs through each client and grabs the content, it is in an infinite loop on each of the multiple client threads, so it's run simultaneously:

(doseq [c @clients] (print ((deref c) :content)) (flush)) 

I sort of came to the conclusion using Atoms in the threads really makes the program work smoothly and allows non-blocking Reads, so I'm happy with this except I feel that resetting the global client's Atom just so I can remove a single client from the list is a bad move. Is there a more appropriate way to accomplish this using swap! ? I chose list for the clients atom as I'm running doseq on each connected client to grab the content and flush it to the output stream socket.

2 Answers 2

9

Avoid deref-ing the atom within a swap! or reset!.

Here swap! is going to give you what you need. It takes a function that receives the current value, which you can use for your update:

(def clients (atom '(:a :b :c :d))) (swap! clients (fn [s] (remove #{:a} s))) 

You may be used to not seeing the function argument of swap! as explicitly as above because swap! will apply the function to any additional args provided, so if they are in the correct order, e.g. if we used set for clients, we can

(def clients (atom #{:a :b :c :d})) (swap! clients disj :a) 
Sign up to request clarification or add additional context in comments.

3 Comments

Oh I really like that, thanks so much. I also did not want to reference clients but could not think of another way to perform this operation. Thanks!
Yes, most of what I found online dealt with hashmaps and most of anything but Lists :-\
(swap! clients (partial remove #{:a})) makes it more clear imo.
3

Sure, you can use swap!, see A. Webb's answer.

You might want to consider whether it's the best choice to store you clients in a list; a set or a map would be a more natural choice (for use with disj / dissoc). (Unless there is always a very small number of clients, in which case it may make sense to use the least complicated data structure available.)

Also, the dosync does nothing here. dosync is for use with Refs (and alter, commute, ref-set, ensure).

I'll also point out that if you run a loop like this:

(doseq [c @clients] ...) 

then it'll always loop across the value of clients at the time the doseq form was entered, regardless of any swap!s to the clients Atom which might have occurred in the meantime. Not that it's likely to be a problem, just something to keep in mind.

Another thing to keep in mind is that Clojure's reference types are designed to (1) hold immutable data, (2) be updated with pure functions (in swap! / alter / send and friends). Putting Atoms in Atoms breaks (1); it may not be a problem here, but it's your responsibility to make sure it isn't. (Breaking (2) would be a problem pretty much always, except in special cases like debug traces which you totally want to be printed again on failed CAS etc.)

7 Comments

Thanks for your input, I was hoping dosync would help while referencing the client atom. I see what you're saying after reading the function more closely. I chose List as I continuously run a doseq on it from multiple threads, seems natural instead of creating a seq out of a vector or map. I feel like I read somewhere that doseq on anything but a list takes a small performance hit.
Actually a doseq on a vector is particularly fast because of chunking. On a set or map -- might be slower, sure, but it's not a big deal (your dosync body will surely dominate if it does any work at all). And disj from a set will be a lot faster than remove from a long list.
I understand, I'm find with getting a 'snapshot' during the doseq stage, I run the loop around doseq continuously so each time it's run it should reference new values, which it does.
How does atoms in atoms break immutability? I appreciate your input, as I'm a novice. Basically I have 1 atom that holds all client atoms. The main atom called Clients is only updated when a new client joins or disconnects. Each client themselves updates their own atom with updated information. To me this made sense for what I was trying to accomplish-- share all data amongst everyone associated.
@scape Atoms are mutable themselves, but their content is meant to be immutable. An atom within an atom means the outer atom would have content which is mutable, i.e. the inner atom. This would defeat the point -- atoms are intended to provide controlled access to state, but in this case the state of the outer atom can be changed by mutating the inner atom without consulting the outer. The second problem is that fact that swap! etc may be issued multiple times behind the scenes, so should be free of side effects. An example of a side effect is changing state. Risk of very subtle bugs.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.