线程属性-调度 下面我们来看一下我们也许希望改变的第二个线程属性:调度。改变调度属性与设置分离属性相类似,但是我们可以使用另外两个函数来查找可用的等级级别,sched_get_priority_max与sched_get_priority_min。 试验--调度 因为thread6.c是与前面的例子十分类似,这里我们只看一下其中的区别。 1 首先,我们需要一些额外的变量: int max_priority; int min_priority; struct sched_param scheduling_value; 2 在我们设置分离属性之后,我们设置调度策略。 res = pthread_attr_setschedpolicy(&thread_attr, SCHED_OTHER); if (res != 0) { perror("Setting scheduling policy failed"); exit(EXIT_FAILURE); } 3 接下来我们查看允许级别的范围: max_priority = sched_get_priority_max(SCHED_OTHER); min_priority = sched_get_priority_min(SCHED_OTHER); 4 并且设置一个级别: scheduling_value.sched_priority = min_priority; res = pthread_attr_setschedparam(&thread_attr, &scheduling_value); if (res != 0) { perror("Setting scheduling priority failed"); exit(EXIT_FAILURE); } 工作原理 这个例子与设置分离状态属性相类似,所不同的是我们设置调度策略。 关闭线程 有时我们希望一个线程可以请求另一个线程结束,而不是向其发送一个信号。线程有一个方法可以完成这个任务,而且并行使用信号处理,线程具有一个在他们被请求结束时修改其行为的方法。 首先我们来看一下请求线程结束的函数: #include int pthread_cancel(pthread_t thread);
这个函数声明很显示:给定一个线程标识符,我们可以请求其关闭。在收到关闭请求之后,事情有一些复杂,但是并不是很多。一个线程可以使用pthread_setcancelstate来设置其关闭状态。
#include int pthread_setcancelstate(int state, int *oldstate);
第一个参数或者为PTHREAD_CANCEL_ENABLE,这个参数允许线程接收关闭请求,或者是PTHREAD_CANCEL_DISABLE,这个参数会使得函数忽略关闭请求。oldstate指针允许获取前一个状态。如果我们对这个状态不感兴趣,我们可以简单的传递一个NULL。如果接受了关闭请求,线程可以采用一个第二级控制,关闭类型,这是由pthread_setcanceltype来设置的。
#include int pthread_setcanceltype(int type, int *oldtype);
这个类型的值可以为PTHREAD_CANCEL_ASYNCHRONOUS,这个类型可以在接收到关闭请求后立即执行,或者为 PTHREAD_CANCEL_DEFERRED,这个类型会使得关闭请求等待直到执行下列函数中的一个:pthread_join,pthread_cond_wait,pthread_cond_timedwait,pthread_testcancel,sem_wait 或者是sigwait。
在这一章我们并没有看到这些函数,因为并不是所有的函数都需要。如以前的一样,更为详细的信息可以在手册中查看。
再一次说明,oldtype允许获取前一个状态,如果我们对前一个状态不感兴趣,我们可以传递一个NULL。默认情况下,线程以关闭状态PTHREAD_CANCEL_ENABLE与关闭状态PTHREAD_CANCEL_DEFERRED开始。
试验--关闭线程
我们这里的程序thread7.c是由thread1.c发展来的。这一次,主线程向其所创建的线程发送一个关闭请求。
#include #include #include #include #include
void *thread_function(void *arg);
int main() { int res; pthread_t a_thread; void *thread_result;
res = pthread_create(&a_thread,NULL,thread_function,NULL); if(res != 0) { perror("Thread creation failed"); exit(EXIT_FAILURE); } sleep(3); printf("Canceling thread...\n"); res = pthread_cancel(a_thread); if(res != 0) { perror("Thread cancelation failed"); exit(EXIT_FAILURE); } printf("Waiting for thread to finish...\n"); res = pthread_join(a_thread,&thread_result); if(res != 0) { perror("Thread join failed"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); }
void *thread_function(void *arg) { int i, res; res = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL); if(res != 0) { perror("Thread pthread_setcancelstate failed"); exit(EXIT_FAILURE); } res = pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL); if(res != 0) { perror("Thread pthread_setcanceltype failed"); exit(EXIT_FAILURE); } printf("Thread_function is running\n"); for(i=0;i<10;i++) { printf("Thread is still running (%d)...\n",i); sleep(1); } pthread_exit(0); }
工作原理
在用通常的方法创建一个新线程之后,主线程休眠(允许新线程启动)并且发送一个关闭请求。
sleep(3); printf("Canceling thread...\n"); res = pthread_cancel(a_thread); if(res != 0) { perror("Thread cancelation failed"); exit(EXIT_FAILURE); }
在所创建的线程中,我们首先设置关闭状态允许关闭。
res = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL); if(res != 0) { perror("Thread pthread_setcancelstate failed"); exit(EXIT_FAILURE); }
然后我们设置关闭类型。
res = pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL); if(res != 0) { perror("Thread pthread_setcanceltype failed"); exit(EXIT_FAILURE); }
最后线程等待关闭。
for(i=0;i<10;i++) { printf("Thread is still running (%d)...\n",i); sleep(1); }
多线程
直到现在,我们总是使得程序的一个执行线程只创建一个线程。然而,我们不希望使得大家认为我们只能创建一个线程。
试验--多线程
作为我们本章的最后一个例子,thread8.c,我们演示如何在同一个程序中创建多个线程,并且以他们启动的不同顺序来收集这些线程。
#include #include #include #include #include
#define NUM_THREADS 6
void *thread_function(void *arg);
int main() { int res; pthread_t a_thread[NUM_THREADS]; void *thread_result; int lots_of_threads;
for(lots_of_threads = 0;lots_of_threads < NUM_THREADS; lots_of_threads++) { res = pthread_create(&(a_thread[lots_of_threads]),NULL,thread_function,(void *)&lots_of_threads); if(res != 0) { perror("Thread creation failed"); exit(EXIT_FAILURE); } sleep(1); } printf("Waiting for threads to finish...\n"); for(lots_of_threads = NUM_THREADS -1;lots_of_threads >=0;lots_of_threads--) { res = pthread_join(a_thread[lots_of_threads],&thread_result); if(res == 0) { printf("Picked up a thread\n"); } else { perror("pthread_join failed"); } } printf("All done\n"); exit(EXIT_SUCCESS); }
void *thread_function(void *arg) { int my_number = *(int *)arg; int rand_num;
printf("thread_function is running. Argument was %d\n",my_number); rand_num = 1+(int)(9.0*rand()/(RAND_MAX+1.0)); sleep(rand_num); printf("Bye from %d\n",my_number); pthread_exit(NULL); }
当我们运行这个程序时,我们会得到下面的输出:
$ ./thread8 thread_function is running. Argument thread_function is running. Argument thread_function is running. Argument thread_function is running. Argument thread_function is running. Argument Bye from 1 thread_function is running. Argument Waiting for threads to finish... Bye from 5 Picked up a thread Bye from 0 Bye from 2 Bye from 3 Bye from 4 Picked up a thread Picked up a thread Picked up a thread Picked up a thread Picked up a thread All done
正如我们所看到的,我们创建了多个线程,并且允许他们以乱序结束 。在这个程序中存在一个bug,如果我们由启动线程的循环中移除sleep调用就会得到验证。我们包含就要演示当我们使用线程编写程序时需要小心。可以定位吗?我们会在下面的"工作原理"部分进行解释。
工作原理
这一次我们创建一个线程ID数组:
pthread_t a_thread[NUM_THREADS];
并且循环来创建多个线程:
for(lots_of_threads = 0; lots_of_threads < NUM_THREADS; lots_of_threads++) { res = pthread_create(&(a_thread[lots_of_threads]), NULL, thread_function, (void *)&lots_of_threads); if (res != 0) { perror("Thread creation failed"); exit(EXIT_FAILURE); } sleep(1); }
这些线程在退出之前等待一个随机时间:
void *thread_function(void *arg) { int my_number = *(int *)arg; int rand_num; printf("thread_function is running. Argument was %d\n", my_number); rand_num=1+(int)(9.0*rand()/(RAND_MAX+1.0)); sleep(rand_num); printf("Bye from %d\n", my_number); pthread_exit(NULL); }
而在主线程中,我们等待收集这些线程,但是并不是以他们被创建的顺序。
for(lots_of_threads = NUM_THREADS - 1; lots_of_threads >= 0; lots_of_threads—) { res = pthread_join(a_thread[lots_of_threads], &thread_result); if (res == 0) { printf("Picked up a thread\n"); } else { perror("pthread_join failed"); } }
如果我们运行没有sleep调用的程序,我们就会看到一些奇怪的作用,包括一些线程使用相同的参数启动。我们是否定位这些情况了呢?线程正在使用一个局部变量作为线程函数的参数进行启动。这个变量是在循环中进行更新的。这个更新发生在下面的代码行中:
for(lots_of_threads = 0; lots_of_threads < NUM_THREADS; lots_of_threads++) { res = pthread_create(&(a_thread[lots_of_threads]), NULL, thread_function, (void *)&lots_of_threads);
如果主线程运行得足够快,他就会为某些线程修改这个参数。类似的行为在使用共享变量与多执行路径时没有引起足够的注意造成的。我们已经警告过大家线程需要小心的设计。要修改这个问题,我们需要像如下的样子来直接传递这个值:
res = pthread_create(&(a_thread[lots_of_threads]), NULL, thread_function, (void *)lots_of_threads);
并且修改thread_function:
void *thread_function(void *arg) { int my_number = (int)arg;
小结
在这一章,我们了解了如何在一个进程内部创建多个执行线程,而其中每个线程共享文件域变量。然后我们探讨了线程控制对临界代码与数据的访问方法,使用信号量与互斥。在此之后,我们讨论了线程属性的控制,而且讨论了我们如何将他们与主线程分离,从而他不再等待他所创建的线程结束。在快速了解了线程如何请求另一个线程完成与接收线程如何管理这些请求之后,我们提供了一个多个并行线程执行的例子。
我们并没有足够的空间来讨论每一个函数调用与相关的线程,但是我们现在应具有足够的知识来使用线程编写我们的程序,并且可以通过阅读手册页来探讨更多的线程知识。 | | | |