In the following example, two threads explicitly synchronize by means of a semaphore, so that the actor is eventually destroyed when the created thread has performed its function. Refer to the semInit(2K) man page for more information.
(file: progov/semaphore.c) #include <stdio.h> #include <stdlib.h> #include <chorus.h> #define USER_STACK_SIZE (1024 * sizeof(long)) KnSem sampleSem; /* Semaphore allocated as global variable */ int childCreate(KnPc entry) { KnActorPrivilege actorP; KnDefaultStartInfo_f startInfo; char* userStack; int childLid = -1; int res; startInfo.dsType = K_DEFAULT_START_INFO; startInfo.dsSystemStackSize = K_DEFAULT_STACK_SIZE; res = actorPrivilege(K_MYACTOR, &actorP, NULL); if (res != K_OK) { printf("Cannot get the privilege of the actor, error %d\n", res); exit(1); } if (actorP == K_SUPACTOR) { startInfo.dsPrivilege = K_SUPTHREAD; } else { startInfo.dsPrivilege = K_USERTHREAD; } if (actorP != K_SUPACTOR) { userStack = malloc(USER_STACK_SIZE); if (userStack == NULL) { printf("Cannot allocate user stack\n"); exit(1); } startInfo.dsUserStackPointer = userStack + USER_STACK_SIZE; } startInfo.dsEntry = entry; res = threadCreate(K_MYACTOR, &childLid, K_ACTIVE, 0, &startInfo); if (res != K_OK) { printf("Cannot create the thread, error %d\n", res); exit(1); } return childLid; } void sampleThread() { int myThreadLi; int res; myThreadLi = threadSelf(); printf("I am the new thread. My thread identifier is: %d\n", myThreadLi); res = semV(&sampleSem); if (res != K_OK){ printf("Cannot perform the semV operation, error %d\n", res); exit(1); } /* Suicide */ res = threadDelete(K_MYACTOR, K_MYSELF); if (res != K_OK){ printf("Cannot suicide, error %d\n", res); exit(1); } /* Should never reach this point! */ } int main(int argc, char** argv, char**envp) { int myThreadLi; int newThreadLi; int res; /* * Initialize the semaphore to 0 so that * the first semP() operation blocks. */ res = semInit(&sampleSem, 0); if (res != K_OK) { printf("Cannot initialize the semaphore, error %d\n", res); exit(1); } newThreadLi = childCreate((KnPc)sampleThread); myThreadLi = threadSelf(); printf("Parent thread identifier = %d, Child thread identifier = %d\n", myThreadLi, newThreadLi); /* * Since semaphore has been initialized to 0 * this semP will block until a semV is performed * by the created thread, letting the main thread know * that created thread's job is done. */ res = semP(&sampleSem, K_NOTIMEOUT); if (res != K_OK) { printf("Cannot perform the semP operation, error %d\n", res); exit(1); } /* * Created thread has run and done all of its job. * It is time to safely exit. */ return 0; }
The semaphore sampleSem is allocated as global data of the actor. Because the address space of the actor is shared by all threads running within the actor, both threads can freely access the semaphore for synchronization.
Avoid performing the semaphore initialization after creating a child thread. Depending on the scheduling, the second thread may start its execution as soon as it is created, and could reach the semV() operation before the semaphore has been initialized. Although the semV() appears to work in this case, semP() would never return because semInit() would reset the counter to 0.
The synchronization works regardless of the order in which the semP() and semV() operations are executed. If semP() is performed first, the counter will be set to -1 and the main thread will be blocked. The semV() call is used to wake the main thread. If scheduling is reversed, the semV() will set the counter to 1, so that when the semP() operation occurs, the counter will be decremented to 0, but the thread will not be blocked.