The lpThreadId parameter of the CreateThread function should always be used for backwards compatability (MSDN: "Windows Me/98/95: This parameter may not be NULL") with older Windows systems. You do not nessecarily have to use it, it just has to be a pointer to a DWORD, normally though, I always give some kind of message in my diagnostics programs stating the thread ID to make my code more opaque during runtime.
As for the semaphore question: you should REALLY question using Semaphores, Mutexes or Events in comparison with something like the Interlocked functions or Critical Sections. Mutexes and Semaphores are what a big windows programmer would call 'kernel objects.' Kernel objects exist as far as the kernel is considered, and they're called kernel objects because the kernel synchronizes access to them using kernel synchronization functions (namely, the WaitFor[Single|Multiple]Object[s](...) function and the Release*(...) functions). The astute reader would realize that naturally for the kernel to schedule such things it would take more CPU time, therefore, you should use them when you need them.
Impractical use of kernel object synchronization between threads
Using them to access a simple global variable. Example:
Code:
MYTYPE g_x;
HANDLE g_hMutex;
//...
void funcinator1() {
-- WaitForSingleObject(g_hMutex,INFINATE);
//Computate on g_x;
-- ReleaseMutex(g_hMutex);
}
void funcinator2() {
-- WaitForSingleObject(g_hMutex,INFINATE);
//Computate on g_x;
-- ReleaseMutex(g_hMutex);
}
int WINAPI WinMain(...) {
-- g_hMutex = CreateMutex(NULL,FALSE,NULL);
-- HANDLE hThread1 = CreateThread(...,(LPTHREAD_START_ROUTINE)funcinator1,...);
-- //...
-- HANDLE hThread1 = CreateThread(...,(LPTHREAD_START_ROUTINE)funcinator2,...);
-- //...
}
This code uses kernel objects to synchronize access to a simple global object, when critical sections will do. Kernel objects take more time to synchronize than something like a critical section, and therefore should be used when you have to wait on a kernel object. A kernel object is created and associated with a HANDLE when you call a function that creates a kernel object, the simplest example is a Thread. A call to CreateThread creates a kernel object and it is associated with the returned HANDLE, therefore it is logical to be able to use a Wait function to wait for that HANDLE to return because a kernel object is created on CreateThread and is released when the thread returns.
Correct usage of kernel objects
Using them to wait for something like a thread to return. Example:
Code:
void funcinator() {
-- //...
}
int WINAPI WinMain(...) {
-- HANDLE hThread = CreateThread(...,(LPTHREAD_START_ROUTINE)funcinator,...);
-- WaitForSingleObject(hThread,INFINATE);
}
A semaphore would be useful in this canonical situation: writing a webserver. You could have it so that a call to CreateSemaphore lets 3 threads through and has them handle requests. So on a clients' request, one thread would be created and it would wait for a semaphore and if it is open it will be let through to process the clients request. But if 3 clients are already being handled, if a fourth client connects the thread will be created, but it will have to wait for another thread to let go of it's semaphore object and decrease it's lock count (and logically, when a semaphore is 'grabbed it's lock count increases.)
Mutexes apply the same way. They (like Semaphores) ensure that a thread has exclusive access to a resource (contra semaphores in which x amount of threads can access one resource.) But like said earlier, why would you need a mutex if a critical section already exists because they have very similar semantics? Well, for one, you can wait on a mutex object for x milliseconds before giving up, while with a critical section you have to wait, so you could have an application wait for an object for 100 milliseconds in a loop, and when that expires process it's messages then continuing in that loop (of course, you can have a thread wait for a critical section and continue immediately if it's not available, but that's not the point.) Second, mutexes are cross-process objects. I can use CreateRemoteThread to create a thread in a remote process, but I'll still have the ability to use WaitForSingleObject on it because the HANDLE returned is associated with that kernel object.
Again, question the use of semaphores, they're extremely slow compared to something like a Critical section, but if you need x amount of threads to run something while the others wait, then you should use a Semaphore.
EDIT: A side note on threading: don't do it unless it's not possible any other way, I might be stating the obvious, but seriously, exhaust every resource (pun not intended) you have before having to resorting to threads. You easily tack on weeks of debugging and auditing for the addition of even a single thread. If you have to, use fibers. Fibers are like 'miniature' threads within threads. A process has threads, and threads have fibers. Ergo:
Process
|
+--Thread1
|
+Fiber1
+Fiber2
+Fiber3
+Fiber4
+--Thread2
[No fibers]
Fibers are practically like threads, only you define your OWN scheduling algorithm, because the kernel synchronizes threads, but fibers don't exist as far as the kernel is concerned. Their context switches are also much less expensive than a Threads' is, the only thing is be very hesitent about mixing threads with fibers. Because if in the above example, Fiber 1 of thread 1 calls WaitForSingleObject on a resource that Thread2 at that point in time owns,Thread1 hault's, and so do all of it's fibers. This is because WaitForSingleObject has what we call Thread Affinity meaning it pauses the thread, and if you use fibers, fibers compose the thread itself (actually when you call ConvertThreadToFiber I guess you could say the thread doesn't exist anymore, more like a 'psuedo thread,' and it exists so the kernel knows where to return so the fibers can then take over via the internal scheduler, because, again, the kernel doesn't know fibers exist and therefore can't return to one of them.) Fibers also don't need any resource synchronization between fibers because the resource will always be owned by the Thread. You use fibers by calling ConvertThreadToFiber, then calling CreateFiber and finally switching between fibers via the SwitchToFiber function.