004 Linux Network ProgrammingIssues, queries and suggestions related to Linux, Network, Linux Programming, Network Programming, System Programming, Sockets, Threads,
FTP based Client Server project using sockets and threads.
In computer science, a block of code, a variable, or a function is said to be thread-safe if it is guaranteed to work perfectly and flawlessly, without any chance of data corruption or system crashes, even when multiple threads are executing this block of code or variable at precisely the same millisecond.
If a system is not thread-safe, then it might just work perfectly 99% of the time, but that 1% of the time when two threads collide, it can cause a catastrophe that is virtually impossible to debug.
In order to grasp thread safety, you have to grasp that threads exist within your computer's RAM. Unlike a totally separate process, which has its own memory, all 20 of your Worker Threads exist within the exact same server process. They share memory, they share global variables, they share heap memory, and they share terminal output.
A variable declared as int q_count = 0; is totally defenseless. There is nothing, no CPU action, that protects this variable. Suppose Thread A and Thread B wish to change q_count. The CPU's Read/Modify/Write cycle for Thread A and Thread B will conflict, causing a permanent corruption of the value of this variable.
In cases where multiple threads are required to share a resource, you make that resource thread-safe by putting a Mutex lock on that resource, creating a "Critical Section."
The Task Queue: Your client_queue is a shared resource. You have artificially serialized all threads by forcing them to all call pthread_mutex_lock(&queue_mutex) before accessing the shared resource. Only one thread is ever inside the queue at a time. The queue is thread-safe.
The Logger: The shared resource is the terminal screen, or stdout. You have put a Mutex lock on your Log() function, forcing the OS to not interleave characters. The Logger is thread-safe.
The most thread-safe approach for your code is to avoid sharing at all!
Examine how you declared your socket variable in your workerThread.c file:
void *workerThread(void *arg) {
while(1) {
int clientSock; // Naturally thread-safe!
// ...
As you can see, clientSock is declared within your function. In fact, every single one of your 20 worker threads has its own private copy of clientSock, stored on its own memory stack. There is zero possibility of Thread A reading Thread B's client ID.
With your original "Thread-Per-Connection" approach, you avoided many thread safety problems since every thread was completely separate, serving only one client in a linear fashion.
But the moment you introduced the Thread Pool, you introduced shared global resources (the Queue). Building a proper enterprise-level server requires you to explicitly code your application to ensure shared resources are strictly guarded (Mutex locks) and private resources are strictly isolated (Thread Stacks). Achieving true thread safety is what allows you to serve 10,000 clients at once with not a single mathematical error or dropped connection!