Is thinking of an OOP object as a “small program” a good analogy?
Yes and no. For a beginner, the analogy helps to highlight that applications break down tasks into smaller tasks. But for an OOP developer, the analogy can lead to misunderstanding of OOP principles.
Programs are a routine, which is developed by creating individual subroutines. In that sense, you are correct that a program is comprised of mini me versions of itself, regardless of whether OOP or FP was used.
However, a routine is conceptually different from an object, so you need to carefully beware to not overextend the analogy.
A routine is a function, whereas an object is a state. In that sense, they are antithetical to one another. If you equate one to the other, you're liable to end up writing bad OOP objects that are "function bags" instead of states. And while some function bags are allowed (e.g. helper methods), those are generally static and thus not instanced (= not an object).
I can send a message to a program telling it to do a task, and then the program will start doing the task and then I (the message sender) can do other things while the program continue doing the task
In a synchronous context, you are assumed to wait and do nothing until the program returns its result. What you're describing is an asynchronous context.
I'm note saying your real-life example is wrong, but I'm not sure if you want your analogy to rely on asynchronicity, as that opens up a whole other can of worms.
When your program is being called by another program instead of being called by you, that "parent" program is just going to halt and wait for a result - unless instructed otherwise, which is the asynchronicity can of worms I'm talking about.
It may be better just to phrase it as "when the program does something, that means the caller (you/another program) is not doing it themselves", without focusing whether you do/n't do other things while waiting for the program.
and later I can send a message to the program asking it if it finished the task,
That again is a very particular scenario and not the most common one. As a general outset, the program returns it's value to you, you don't go fetch it.
There are ways to poll for a result, but this is something you have to build when you need it. The default behavior is pushing (program returns output to you), not pulling (you fetch the output from the program).
This is the same argument as I made before about asynchronicity: it's better to avoid this additional complexity that is not essential to describing what "a program" is. You're opening up new cans of worms left, right, and center, and it's adding unnecessary caveats that make it dangerous to rely on your analogy.
so each program have at least one thread, while objects don't have threads (they all share the same thread, i.e. if one object is "running", then all other objects are "paused")
This is where your analogy is just wrong, or at least misunderstanding exactly what a thread is.
All other objects are not paused. You just relied on the implicit assumption your ability to asynchronously do something else while the program is running. The same principle applies here: in an asynchronous context, multiple objects can be executing their logic at the same time.
I'm not delving into the specific distinction/relation between asynchronicity and multithreading, as that is not the goal of the analogy.
Objects don't "have" threads. Objects live in the runtime, and threads are the workers of that runtime. Think of it like mechanics working in a garage. They are individual workers, but they share the same wall of wrenches. Those wrenches are your objects, and the mechanics are your threads.
A wrench does not own a mechanic. A mechanic does not own their own wrench (they are the garage's property), though they can temporarily use a wrench when they need to.
Similarly, an object does not have a thread. A thread does not own an object (they are the runtime's property), but it can use an object when it needs to.