⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 pth.pod

📁 Linux下的中文输入法
💻 POD
📖 第 1 页 / 共 5 页
字号:
signal (for kernel-space threads) or a software interrupt signal (foruser-space threads), like C<SIGALRM> or C<SIGVTALRM>. In non-preemptivescheduling, once a thread received control from the scheduler it keepsit until either a blocking situation occurs (again a function call whichwould block and instead switches back to the scheduler) or the threadexplicitly yields control back to the scheduler in a cooperative way.=item B<o> B<concurrency> vs. B<parallelism>Concurrency exists when at least two threads are I<in progress> at thesame time. Parallelism arises when at least two threads are I<executing>simultaneously. Real parallelism can be only achieved on multiprocessormachines, of course. But one also usually speaks of parallelism orI<high concurrency> in the context of preemptive thread schedulingand of I<low concurrency> in the context of non-preemptive threadscheduling.=item B<o> B<responsiveness>The responsiveness of a system can be described by the user visibledelay until the system responses to an external request. When this delayis small enough and the user doesn't recognize a noticeable delay,the responsiveness of the system is considered good. When the userrecognizes or is even annoyed by the delay, the responsiveness of thesystem is considered bad.=item B<o> B<reentrant>, B<thread-safe> and B<asynchronous-safe> functionsA reentrant function is one that behaves correctly if it is calledsimultaneously by several threads and then also executes simultaneously.Functions that access global state, such as memory or files, of course,need to be carefully designed in order to be reentrant. Two traditionalapproaches to solve these problems are caller-supplied states andthread-specific data.Thread-safety is the avoidance of I<data races>, i.e., situationsin which data is set to either correct or incorrect value dependingupon the (unpredictable) order in which multiple threads access andmodify the data. So a function is thread-safe when it still behavessemantically correct when called simultaneously by several threads (itis not required that the functions also execute simultaneously). Thetraditional approach to achieve thread-safety is to wrap a function bodywith an internal mutual exclusion lock (aka `mutex'). As you shouldrecognize, reentrant is a stronger attribute than thread-safe, becauseit is harder to achieve and results especially in no run-time contentionbetween threads. So, a reentrant function is always thread-safe, but notvice versa.Additionally there is a related attribute for functions namedasynchronous-safe, which comes into play in conjunction with signalhandlers. This is very related to the problem of reentrant functions. Anasynchronous-safe function is one that can be called safe and withoutside-effects from within a signal handler context. Usually very fewfunctions are of this type, because an application is very restricted inwhat it can perform from within a signal handler (especially what systemfunctions it is allowed to call). The reason mainly is, because only afew system functions are officially declared by POSIX as guaranteed tobe asynchronous-safe. Asynchronous-safe functions usually have to bealready reentrant.=back=head2 User-Space ThreadsUser-space threads can be implemented in various way. The twotraditional approaches are:=over 3=item B<1.>B<Matrix-based explicit dispatching between small units of execution:>Here the global procedures of the application are split into smallexecution units (each is required to not run for more than a fewmilliseconds) and those units are implemented by separate functions.Then a global matrix is defined which describes the execution (andperhaps even dependency) order of these functions. The main serverprocedure then just dispatches between these units by calling onefunction after each other controlled by this matrix. The threads arecreated by more than one jump-trail through this matrix and by switchingbetween these jump-trails controlled by corresponding occurred events.This approach gives the best possible performance, because one canfine-tune the threads of execution by adjusting the matrix, and thescheduling is done explicitly by the application itself. It is also veryportable, because the matrix is just an ordinary data structure, andfunctions are a standard feature of ANSI C.The disadvantage of this approach is that it is complicated to writelarge applications with this approach, because in those applicationsone quickly gets hundreds(!) of execution units and the control flowinside such an application is very hard to understand (because it isinterrupted by function borders and one always has to remember theglobal dispatching matrix to follow it). Additionally, all threadsoperate on the same execution stack. Although this saves memory, it isoften nasty, because one cannot switch between threads in the middle ofa function. Thus the scheduling borders are the function borders.=item B<2.>B<Context-based implicit scheduling between threads of execution:>Here the idea is that one programs the application as with forkedprocesses, i.e., one spawns a thread of execution and this runs from thebegin to the end without an interrupted control flow. But the controlflow can be still interrupted - even in the middle of a function.Actually in a preemptive way, similar to what the kernel does for theheavy-weight processes, i.e., every few milliseconds the user-spacescheduler switches between the threads of execution. But the threaditself doesn't recognize this and usually (except for synchronizationissues) doesn't have to care about this.The advantage of this approach is that it's very easy to program,because the control flow and context of a thread directly followsa procedure without forced interrupts through function borders.Additionally, the programming is very similar to a traditional and wellunderstood fork(2) based approach.The disadvantage is that although the general performance is increased,compared to using approaches based on heavy-weight processes, it is decreasedcompared to the matrix-approach above. Because the implicit preemptivescheduling does usually a lot more context switches (every user-space contextswitch costs some overhead even when it is a lot cheaper than a kernel-levelcontext switch) than the explicit cooperative/non-preemptive scheduling.Finally, there is no really portable POSIX/ANSI-C based way to implementuser-space preemptive threading. Either the platform already has threads,or one has to hope that some semi-portable package exists for it. Andeven those semi-portable packages usually have to deal with assemblercode and other nasty internals and are not easy to port to forthcomingplatforms.=backSo, in short: the matrix-dispatching approach is portable and fast, butnasty to program. The thread scheduling approach is easy to program,but suffers from synchronization and portability problems caused by itspreemptive nature.=head2 The Compromise of PthBut why not combine the good aspects of both approaches while avoidingtheir bad aspects? That's the goal of B<Pth>. B<Pth> implementseasy-to-program threads of execution, but avoids the problems ofpreemptive scheduling by using non-preemptive scheduling instead.This sounds like, and is, a useful approach. Nevertheless, one has tokeep the implications of non-preemptive thread scheduling in mind whenworking with B<Pth>. The following list summarizes a few essentialpoints:=over 2=item B<o>B<Pth provides maximum portability, but NOT the fanciest features>.This is, because it uses a nifty and portable POSIX/ANSI-C approach forthread creation (and this way doesn't require any platform dependentassembler hacks) and schedules the threads in non-preemptive way (whichdoesn't require unportable facilities like C<SIGVTALRM>). On the otherhand, this way not all fancy threading features can be implemented.Nevertheless the available facilities are enough to provide a robust andfull-featured threading system.=item B<o>B<Pth increases the responsiveness and concurrency of an event-drivenapplication, but NOT the concurrency of number-crunching applications>.The reason is the non-preemptive scheduling. Number-crunchingapplications usually require preemptive scheduling to achieveconcurrency because of their long CPU bursts. For them, non-preemptivescheduling (even together with explicit yielding) provides only the oldconcept of `coroutines'. On the other hand, event driven applicationsbenefit greatly from non-preemptive scheduling. They have only shortCPU bursts and lots of events to wait on, and this way run faster undernon-preemptive scheduling because no unnecessary context switchingoccurs, as it is the case for preemptive scheduling. That's why B<Pth>is mainly intended for server type applications, although there is notechnical restriction.=item B<o>B<Pth requires thread-safe functions, but NOT reentrant functions>.This nice fact exists again because of the nature of non-preemptivescheduling, where a function isn't interrupted and this way cannot bereentered before it returned. This is a great portability benefit,because thread-safety can be achieved more easily than reentrancepossibility. Especially this means that under B<Pth> more existingthird-party libraries can be used without side-effects than it's the casefor other threading systems.=item B<o>B<Pth doesn't require any kernel support, but can NOTbenefit from multiprocessor machines>.This means that B<Pth> runs on almost all Unix kernels, because thekernel does not need to be aware of the B<Pth> threads (because theyare implemented entirely in user-space). On the other hand, it cannotbenefit from the existence of multiprocessors, because for this, kernelsupport would be needed. In practice, this is no problem, becausemultiprocessor systems are rare, and portability is almost moreimportant than highest concurrency.=back=head2 The life cycle of a threadTo understand the B<Pth> Application Programming Interface (API), ithelps to first understand the life cycle of a thread in the B<Pth>threading system. It can be illustrated with the following directedgraph:             NEW              |              V      +---> READY ---+      |       ^      |      |       |      V   WAITING <--+-- RUNNING                     |      :              V   SUSPENDED       DEADWhen a new thread is created, it is moved into the B<NEW> queue of thescheduler. On the next dispatching for this thread, the scheduler picksit up from there and moves it to the B<READY> queue. This is a queuecontaining all threads which want to perform a CPU burst. There they arequeued in priority order. On each dispatching step, the scheduler alwaysremoves the thread with the highest priority only. It then increases thepriority of all remaining threads by 1, to prevent them from `starving'.The thread which was removed from the B<READY> queue is the newB<RUNNING> thread (there is always just one B<RUNNING> thread, ofcourse). The B<RUNNING> thread is assigned execution control. Afterthis thread yields execution (either explicitly by yielding executionor implicitly by calling a function which would block) there are threepossibilities: Either it has terminated, then it is moved to the B<DEAD>queue, or it has events on which it wants to wait, then it is moved intothe B<WAITING> queue. Else it is assumed it wants to perform more CPUbursts and immediately enters the B<READY> queue again.Before the next thread is taken out of the B<READY> queue, theB<WAITING> queue is checked for pending events. If one or more eventsoccurred, the threads that are waiting on them are immediately moved tothe B<READY> queue.The purpose of the B<NEW> queue has to do with the fact that in B<Pth>a thread never directly switches to another thread. A thread alwaysyields execution to the scheduler and the scheduler dispatches to thenext thread. So a freshly spawned thread has to be kept somewhere untilthe scheduler gets a chance to pick it up for scheduling. That iswhat the B<NEW> queue is for.The purpose of the B<DEAD> queue is to support thread joining. When athread is marked to be unjoinable, it is directly kicked out of thesystem after it terminated. But when it is joinable, it enters theB<DEAD> queue. There it remains until another thread joins it.Finally, there is a special separated queue named B<SUSPENDED>, to wherethreads can be manually moved from the B<NEW>, B<READY> or B<WAITING>queues by the application. The purpose of this special queue is totemporarily absorb suspended threads until they are again resumed bythe application. Suspended threads do not cost scheduling or eventhandling resources, because they are temporarily completely out of thescheduler's scope. If a thread is resumed, it is moved back to the queuefrom where it originally came and this way again enters the schedulersscope.=head1 APPLICATION PROGRAMMING INTERFACE (API)In the following the B<Pth> I<Application Programming Interface> (API)is discussed in detail. With the knowledge given above, it should nowbe easy to understand how to program threads with this API. In goodUnix tradition, B<Pth> functions use special return values (C<NULL>in pointer context, C<FALSE> in boolean context and C<-1> in integercontext) to indicate an error condition and set (or pass through) theC<errno> system variable to pass more details about the error to thecaller.=head2 Global Library ManagementThe following functions act on the library as a whole.  They are used toinitialize and shutdown the scheduler and fetch information from it.=over 4=item int B<pth_init>(void);This initializes the B<Pth> library. It has to be the first B<Pth> APIfunction call in an application, and is mandatory. It's usually done atthe begin of the main() function of the application. This implicitlyspawns the internal scheduler thread and transforms the single executionunit of the current process into a thread (the `main' thread). Itreturns C<TRUE> on success and C<FALSE> on error.=item int B<pth_kill>(void);This kills the B<Pth> library. It should be the last B<Pth> API function callin an application, but is not really required. It's usually done at the end ofthe main function of the application. At least, it has to be called from withinthe main thread. It implicitly kills all threads and transforms back thecalling thread into the single execution unit of the underlying process.  Theusual way to terminate a B<Pth> application is either a simple`C<pth_exit(0);>' in the main thread (which waits for all other threads toterminate, kills the threading system and then terminates the process) or a

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -