Skip to main content
gamedev.net
🔒 Locked 🎮 Unity

Socket/thread design question

Started by CapBBeard 6 replies 900+ views
Original Post
CapBBeard
CapBBeard
Hey everyone, I'm currently working on a server status/query tool for an NZ gaming community, and at the moment I've got it to the point where it can query a server and gather info from it (you know, number of players etc). I now want to make it multithreaded so I can query more than one at a time, and also run the GUI frontend fluidly. At the moment I have a class something like this:

class _rcExport ServerQuery
{
public:
	ServerQuery() {}
	virtual ~ServerQuery() {}

	virtual const std::vector<PlayerData*>&		getPlayerData() const = 0;
	virtual const ServerData*			getServerData() const = 0;
	virtual const unsigned long			ping() = 0;
	virtual void					update() = 0;

	virtual const std::string&			getAddress() const = 0;
	virtual const short				getPort() const = 0;
	};
In the concrete implementations, update() opens a UDP socket to the server, queries it for information, and fills both a ServerData struct and a vector with player information. Other classes can then grab that information using the get methods. I'm now having trouble working out how to turn this into a safe multithreaded system. I've looked at Boost.Threads and read several threading tutorials, but they've showed me more about the usage of threads as opposed to useful implementation ideas. I haven't had a lot of experience with threads, as I've only ever had to use a single extra thread until now (perhaps I still do?). Mainly I'm just after a bit of direction and maybe a few hints :P Some idea's I've had include writing a thread safe Map class which can be read by the gui and updated by a server query manager, only this makes the above server query class redundant. I've also thought about implementing some kind of thread/server pool (I've heard a bit about semaphores or a mutex + condition variable to achieve this), or writing a thread safe queue for update() to insert it's results into, then for the ServerQuery object to read back out (which seems overly complex). I'm not sure any of these methods are any good, or worth pursuing, but hopefully they show that I've put some thought into this myself before posting. Any help would be great :) [Edited by - CapBBeard on August 27, 2006 10:57:29 AM]
hplus0603
hplus0603
There are two problems with thread safety:

1) Non-re-entrant code. I e, a container doesn't like one thread adding an item, while another thread is attempting to iterate the items.

2) Object lifetime. For example, one thread may retrieve a pointer to some object, and want to use it. Another thread will then delete the object from a container, and delete the underlying object. The first thread will then have a pointer to a stale object.

The solution to 1) is either to design re-entrant containers (which can be a significant challenge), or wrap your container in critical sections (much easier, but watch out for contention!)

The solution to 2) is either very, very careful planning of object ownership, or, more commonly, some form of (thread safe) reference counting.

In your design, it looks a lot to me list you'll run into trouble with case 2), so I recommend reference counting your objects, and adding an "active" flag on the object which tells it whether it's still in the list of "active" objects seen from the connection, or whether it's been removed and is just a stale instance.
enum Bool { True, False, FileNotFound };
hplus0603
hplus0603
Quote:
Original post by Anonymous Poster
Another solution besides reference counting is to provide a notification mechanism so that the application UI is notified of *changes* to the status of a particular host in the list of hosts. If a host gets terminated or gets new information, then the UI can get advised of that change and adjust the UI elements properly. This can be implemented via a notification interface or a function pointer the UI code registers with.


You still don't get around the problem that, if the other thread keeps a pointer to the object used in the first thread, when the first thread deletes the pointer, there's some chance that the other thread will currently be using that object (say, it's in the middle of a screen update) and will crash by dereferencing a bad pointer.

In general, using notifiers (subscribers, listeners, signals/slots) is a great tool to improve the consistency and responsiveness of your application, but it doesn't solve the multithreaded object ownership problem.
enum Bool { True, False, FileNotFound };
hplus0603
hplus0603
Quote:
... then all notification sinks are advised beforehand so that they can make the necessary adjustments.


So, in this model, the notification sinks, owned by the first thread, get the notification from the second thread, in the context of the second thread, because the second thread wants to delete the object? Thus, the notification sinks could be entered by thread 1 or 2?

Then you just have the reverse problem -- what if the first thread wants to delete the notification sinks? If you use the same mechanism to notify back into the second thread, then you have a deadlock waiting to happen.

If you have some objects that
1) are re-entrant
2) will not go away
then this method will work, because you'll just set it up so that those objects "own" all threading issues.

Ref-counting and asynchronous notifications is a more robust method for the general case.
enum Bool { True, False, FileNotFound };
cruppstahl
cruppstahl
@CapBBeard: multithreading makes things really complicated. If you only use it because you want to query several servers in parallel, you don't need multithreading. You can use non-blocking IO with select (WSASelect on windows). With select you can check if a socket has received incoming data, and then you can use read() or recv() to get the data, without blocking the application. So your event loop is something like

while (running) {
draw gui
select all open sockets
receive data from those sockets which are returned by select
process the data
}

i wouldn't recommend using multithreading unless you really, really need it.
Anon Mike
Anon Mike
Non-blocking sockets is definitely a good option. There are a few corner cases to watch out for though. The biggest is that there is no (standard) async version of gethostbyname which can block for a quite a long time. If you're willing to be Windows-only you can use WSAAsyncGetHostByName instead.
-Mike

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.