Optimizing producer consumer using monitors
for(;;) is the more idiomatic infinite loop, read as "forever".
Avoid long conditional blocks, and unneccessary indentation. What you actually have is a loop followed by the else-block. Which would be best written as:

while (buffer.size() == MAX_BUFFER_SIZE)
    space_in_buffer.wait(lock);
rest-of-loop;


Unless you prefer using a lambda for the condition.

space_in_buffer.wait(lock, [&amp;]{ return buffer.size() == MAX_BUFFER_SIZE; });
rest-of-loop;

You are using the std::queue as a ring-buffer. That's inefficient, especially as the queue still allocates and frees its nodes on .push() and .pop(). Use a dedicated ringbuffer like boost::circular_buffer, orsomething without any dynamic allocation at all.
The format for printing a std::size_t with std::printf is "%zu". Don't drop the length-marker.
Mutex, buffer, and condition-variables should be encapsulated in a class-template with the buffer-size as a parameter for reuse.
Yes, you should minimize the time you hold the lock to maximize concurrency. See whether you can do that with scoping or need to manually release the lock.