Sending function call operator to template

Hi! Building a simple multithreaded client/server program and i'm using a threadpool to handle the connections as they get accepted. I have the following code (credits to Jakob Progsch, https://github.com/progschj) that, from what I understand, is adding a task to the queue that a thread will pick up:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  template<class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args) 
    -> std::future<typename std::result_of<F(Args...)>::type>
{
    using return_type = typename std::result_of<F(Args...)>::type;

    auto task = std::make_shared< std::packaged_task<return_type()> >(
            std::bind(std::forward<F>(f), std::forward<Args>(args)...)
        );
        
    std::future<return_type> res = task->get_future();
    {
        std::unique_lock<std::mutex> lock(queue_mutex);

        // don't allow enqueueing after stopping the pool
        if(stop)
            throw std::runtime_error("enqueue on stopped ThreadPool");

        tasks.emplace([task](){ (*task)(); });
    }
    condition.notify_one();
    return res;
}


I am trying to use it as such: each connection is an object that uses the function call operator to run. How do I send this to the threadpool?

1
2
3
4
ThreadPool threads = ThreadPool(4); //member variable
..
Connection con(new_fd, s, their_addr);//runs using cons()
threads.enqueue(??????);//how do I send con() to the threadpool enqueue? 


Sorry for the silly question lol. Can't figure out the proper syntax. Thank you and let me know if i'm being an idiot love you all
Last edited on
First, you have a Connection object (con), what do you know about it and how do you use it?

By that I mean, do you know, factually, that a con formed on the stack like this is safe to pass as a parameter? If you knew that a con was, under the hood, a wrapper that represented some kind of shared pointer to the connection resource, such that it can be passed this way (as std::string can do), fine. If you don't know that, what happens to con when this falls from scope? (Hint, it evaporates unless you know otherwise).

My point here is that before you can pass it to enqueue, you have to know the storage strategy for it. How does it persist beyond the function this code is in? Should you have a shared_ptr<Connection> hold this new con?

So, let's say you get that out of the way somehow, next...enqueue looks basically like std::bind. If you know how to use std::bind, do that for enqueue. If you're not familiar (and you should look this up), the first parameter is a pointer to a function, or a function object (functor), or a pointer to a member function (where the first parameter you pass next should be the pointer to the instantiation upon which the call will be made).

If any of that isn't clear, then you'll need to experiment with and understand how to use std::bind, because it's clear that ThreadPool uses std::bind (it's really just forwarding your parameters to std::bind).

That means this expects there to be a function to run, and THAT function should probably accept the Connection (and I suspect, more likely, a shared_ptr to a new Connection instance), and do something with it.

Now, more generally, are you not using Asio?
Thank you for the response! I'm not using Asio (I know I should). I'm just using BSD sockets

Here's the overall structure:

-The server is an object and it has a threadpool and a vector that holds the connection objects (and random other things) as member variables.

-The connection object has as its member variables: the file descriptor, the IP address, and the sockaddr_storage struct to just hold info about the socket. Here's the connection class code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
class Connection
{	

	int fd;//files descriptor for the client socket
	char* connection_addr;//IP address of the client
	struct sockaddr_storage socketconnection; //keep just in case :)
	void (Connection::*state)();//state of the connection

	//member functions:
	void main_menu();

public:

	//store the socket file descriptor and the IP address of the client
	Connection(int socket, char* addr, struct sockaddr_storage theiraddr): 
		fd(socket), connection_addr(addr), socketconnection(theiraddr)
	{
		std::cout << "Initialized new connection: " << addr << std::endl;
	}

	//delete the socket file descriptor
	~Connection(){
		std::cout << "Closing connection: " << connection_addr << std::endl;
		try
		{
			close(fd);
		}
		catch(...)
		{
			std::cout << "ERROR CLOSING FILE DESCRIPTOR" << std::endl;
		}
	}

	//Start running the connection thread!
	void operator()()
	{
		state = &Connection::main_menu;
		try
		{
			for(;;)
			{
				(this->*state)();
			}
		}
		catch(...){

		}
	}

};

void Connection::main_menu()
{
	std::string input("");
	std::string menu_options("*****Main Menu*****\n\tPlease choose one of the following: "
		"\n\t-View Rooms\n\t-Make New Room\n");


		if(send(fd, menu_options.c_str(), menu_options.length(), 0) == 0)
		{
			std::cout << "ERROR SENDING MESSAGE TO CLIENT" << std::endl;
			exit(1);
		}
		exit(0);//I know it terminates early - just testing something

}



Std::bind basically takes a function/parameters and turns it into a function object? Since I overloaded Connection () it is already a functor, right?

I've already tried:
1
2
3
4
5
threads.enqueue(std::bind(&Connection::(), con))
threads.enqueue(std::bind(Connection, con))
threads.enqueue(std::bind(con))

etc etc.


I have just been struggling to find a resource online that gave some guidance on the proper
syntax to send the con functor to enqueue. Also, thanks for taking the time to help me :)



Last edited on
Yikes, I forgot to link pthreads and that's why I was getting an error.

threads.enqueue(con) works lol

sorry for the trouble. Any advice would be appreciated, though! I'm trying to learn everything :)
Last edited on
Ok, some advice ;)

First, are you still doing this:
1
2
3
4
ThreadPool threads = ThreadPool(4); //member variable
..
Connection con(new_fd, s, their_addr);//runs using cons()
threads.enqueue(??????)


Since I don't have the context, here, what are you doing to keep con, the Connection, in scope? threads.enqueue will take that as a reference, which is to say it will not copy the connection object unless your function copies it.

I can't be sure, but if your design starts a thread for each new connection it is well known that this limits the number of connections you can handle. If you're taking in something like 10 connections, and the computer is a quad core, you're probably ok. If you want to be able to handle hundreds or thousands, then you'll need to use something like Asio.

I used Asio years ago (I'm a 50 something, been at this a long while), and in several projects since. At that time it was merely a library in Boost. I found it robust, and capable of scaling to massive server creation.

Recently Asio has been accepted into the C++ standard. I'd have to check for the details, as I just noticed this a few weeks ago, but that is either for C++ 17 or C++ 20. In either case, though, even if one is using the older C++ 11 standards, the Boost Asio is still an excellent choice, just as shared_ptr was before that was brought into the standard. (Their primary mistake of that era was auto_ptr).




Topic archived. No new replies allowed.