【Linux应用】pthread详解
【Linux应用】pthread详解
前言
我并不假定你会使用Linux的线程,所以在这里就简单的介绍一下。如果你之前有过多线程方面的编程经验,完全可以忽略本文的内容,因为它非常的初级。
首先说明一下,在Linux编写多线程程序需要包含头文件pthread.h。当然,只包含一个头文件是不能搞定线程的,还需要连接libpthread.so这个库,因此在程序连接阶段应该有类似这样的指令:
gcc program.o -o program -lpthread
1. pthread_create
在Linux下创建的线程的API接口是pthread_create(),它的完整定义是:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*) void *arg);
pthread_create是类Unix操作系统(Unix、Linux、Mac OS X等)的创建线程的函数。它的功能是创建线程(实际上就是确定调用该线程函数的入口点),在线程创建以后,就开始运行相关的线程函数。pthread_create的返回值:若成功,返回0;若出错,返回出错编号。linux下用C语言开发多线程程序,Linux系统下的多线程遵循POSIX线程接口,称为pthread。
- 第一个参数为指向线程标识符的指针。
- 第二个参数用来设置线程属性。
- 第三个参数是线程运行函数的起始地址。
- 最后一个参数是运行函数的参数。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#define NUM_THREADS 8
void *PrintHello(void *args)
{
int thread_arg;
sleep(1);
thread_arg = (int)(*((int*)args));
printf("Hello from thread %d\n", thread_arg);
return NULL;
}
int main(void)
{
int rc,t;
pthread_t thread[NUM_THREADS];
for( t = 0; t < NUM_THREADS; t++)
{
printf("Creating thread %d\n", t);
//此处t变量的用法是方便大家调测代码的写法,实际使用会有问题,因为这个t是局部变量,
函数执行完后马上释放,大家传递参数时需要使用全局变量或malloc出来的变量。
rc = pthread_create(&thread[t], NULL, PrintHello, &t);
if (rc)
{
printf("ERROR; return code is %d\n", rc);
return EXIT_FAILURE;
}
}
sleep(5);
for( t = 0; t < NUM_THREADS; t++)
pthread_join(thread[t], NULL);
return EXIT_SUCCESS;
}
结果为:
$ gcc thread_test.c -o thread_test -std=c99 -pthread
$ ./thread_test
Creating thread 0
Creating thread 1
Creating thread 2
Creating thread 3
Creating thread 4
Creating thread 5
Creating thread 6
Creating thread 7
Hello from thread 8
Hello from thread 8
Hello from thread 8
Hello from thread 8
Hello from thread 8
Hello from thread 8
Hello from thread 8
Hello from thread 8
2. 线程的合并与分离
我们首先要明确的一个问题就是什么是线程的合并。从前面的叙述中读者们已经了解到了,pthread_create()接口负责创建了一个线程。那么线程也属于系统的资源,这跟内存没什么两样,而且线程本身也要占据一定的内存空间。众所周知的一个问题就是C或C++编程中如果要通过malloc()或new分配了一块内存,就必须使用free()或delete来回收这块内存,否则就会产生著名的内存泄漏问题。既然线程和内存没什么两样,那么有创建就必须得有回收,否则就会产生另外一个著名的资源泄漏问题,这同样也是一个严重的问题。那么线程的合并就是回收线程资源了。
线程的合并是一种主动回收线程资源的方案。当一个进程或线程调用了针对其它线程的pthread_join()接口,就是线程合并了。这个接口会阻塞调用进程或线程,直到被合并的线程结束为止。当被合并线程结束,pthread_join()接口就会回收这个线程的资源,并将这个线程的返回值返回给合并者。
与线程合并相对应的另外一种线程资源回收机制是线程分离,调用接口是pthread_detach()。线程分离是将线程资源的回收工作交由系统自动来完成,也就是说当被分离的线程结束之后,系统会自动回收它的资源。因为线程分离是启动系统的自动回收机制,那么程序也就无法获得被分离线程的返回值,这就使得pthread_detach()接口只要拥有一个参数就行了,那就是被分离线程句柄。
线程合并和线程分离都是用于回收线程资源的,可以根据不同的业务场景酌情使用。不管有什么理由,你都必须选择其中一种,否则就会引发资源泄漏的问题,这个问题与内存泄漏同样可怕。
3. 线程的属性
前面还说到过线程是有属性的,这个属性由一个线程属性对象来描述。线程属性对象由pthread_attr_init()接口初始化,并由pthread_attr_destory()来销毁,它们的完整定义是:
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destory(pthread_attr_t *attr);
那么线程拥有哪些属性呢?一般地,Linux下的线程有:绑定属性、分离属性、调度属性、堆栈大小属性和满占警戒区大小属性。下面我们就分别来介绍这些属性。
3.1 绑定属性
说到这个绑定属性,就不得不提起另外一个概念:轻进程(Light Weight Process,简称LWP)。轻进程和Linux系统的内核线程拥有相同的概念,属于内核的调度实体。一个轻进程可以控制一个或多个线程。默认情况下,对于一个拥有n个线程的程序,启动多少轻进程,由哪些轻进程来控制哪些线程由操作系统来控制,这种状态被称为非绑定的。那么绑定的含义就很好理解了,只要指定了某个线程“绑”在某个轻进程上,就可以称之为绑定的了。被绑定的线程具有较高的相应速度,因为操作系统的调度主体是轻进程,绑定线程可以保证在需要的时候它总有一个轻进程可用。绑定属性就是干这个用的。
设置绑定属性的接口是pthread_attr_setscope(),它的完整定义是:
int pthread_attr_setscope(pthread_attr_t *attr, int scope);
它有两个参数,第一个就是线程属性对象的指针,第二个就是绑定类型,拥有两个取值:PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS(非绑定的)。
#include <stdio.h>
#include <pthread.h>
……
int main( int argc, char *argv[] )
{
pthread_attr_t attr;
pthread_t th;
……
pthread_attr_init( &attr );
pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM );
pthread_create( &th, &attr, thread, NULL );
……
}
不知道你是否在这里发现了本文的矛盾之处。就是这个绑定属性跟我们之前说的NPTL有矛盾之处。在介绍NPTL的时候就说过业界有一种m:n的线程方案,就跟这个绑定属性有关。但是笔者还说过NPTL因为Linux的“蠢”没有采取这种方案,而是采用了“1:1”的方案。这也就是说,Linux的线程永远都是绑定。对,Linux的线程永远都是绑定的,所以PTHREAD_SCOPE_PROCESS在Linux中不管用,而且会返回ENOTSUP错误。
既然Linux并不支持线程的非绑定,为什么还要提供这个接口呢?答案就是兼容!因为Linux的NTPL是号称POSIX标准兼容的,而绑定属性正是POSIX标准所要求的,所以提供了这个接口。如果读者们只是在Linux下编写多线程程序,可以完全忽略这个属性。如果哪天你遇到了支持这种特性的系统,别忘了我曾经跟你说起过这玩意儿:)
3.2 分离属性
前面说过线程能够被合并和分离,分离属性就是让线程在创建之前就决定它应该是分离的。如果设置了这个属性,就没有必要调用pthread_join()或pthread_detach()来回收线程资源了。
设置分离属性的接口是pthread_attr_setdetachstate(),它的完整定义是:
pthread_attr_setdetachstat(pthread_attr_t *attr, int detachstate);
它的第二个参数有两个取值:PTHREAD_CREATE_DETACHED(分离的)和PTHREAD_CREATE_JOINABLE(可合并的,也是默认属性)。
3.3 调度属性
线程的调度属性有三个,分别是:算法、优先级和继承权。
Linux提供的线程调度算法有三个:轮询、先进先出和其它。其中轮询和先进先出调度算法是POSIX标准所规定,而其他则代表采用Linux自己认为更合适的调度算法,所以默认的调度算法也就是其它了。轮询和先进先出调度算法都属于实时调度算法。轮询指的是时间片轮转,当线程的时间片用完,系统将重新分配时间片,并将它放置在就绪队列尾部,这样可以保证具有相同优先级的轮询任务获得公平的CPU占用时间;先进先出就是先到先服务,一旦线程占用了CPU则一直运行,直到有更高优先级的线程出现或自己放弃。
设置线程调度算法的接口是pthread_attr_setschedpolicy(),它的完整定义是:
pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
它的第二个参数有三个取值:SCHED_RR(轮询)、SCHED_FIFO(先进先出)和SCHED_OTHER(其它)。
Linux的线程优先级与进程的优先级不一样,进程优先级我们后面再说。Linux的线程优先级是从1到99的数值,数值越大代表优先级越高。而且要注意的是,只有采用SHCED_RR或SCHED_FIFO调度算法时,优先级才有效。对于采用SCHED_OTHER调度算法的线程,其优先级恒为0。
设置线程优先级的接口是pthread_attr_setschedparam(),它的完整定义是:
struct sched_param {
int sched_priority;
}
int pthread_attr_setschedparam(pthread_attr_t *attr, struct sched_param *param);
sched_param结构体的sched_priority字段就是线程的优先级了。
此外,即便采用SCHED_RR或SCHED_FIFO调度算法,线程优先级也不是随便就能设置的。首先,进程必须是以root账号运行的;其次,还需要放弃线程的继承权。什么是继承权呢?就是当创建新的线程时,新线程要继承父线程(创建者线程)的调度属性。如果不希望新线程继承父线程的调度属性,就要放弃继承权。
设置线程继承权的接口是pthread_attr_setinheritsched(),它的完整定义是:
int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);
它的第二个参数有两个取值:PTHREAD_INHERIT_SCHED(拥有继承权)和PTHREAD_EXPLICIT_SCHED(放弃继承权)。新线程在默认情况下是拥有继承权。
代码4能够演示不同调度算法和不同优先级下各线程的行为,同时也展示如何修改线程的调度属性。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#define THREAD_COUNT 12
void show_thread_policy( int threadno )
{
int policy;
struct sched_param param;
pthread_getschedparam( pthread_self(), &policy, ¶m );
switch( policy ){
case SCHED_OTHER:
printf( "SCHED_OTHER %d\n", threadno );
break;
case SCHED_RR:
printf( "SCHDE_RR %d\n", threadno );
break;
case SCHED_FIFO:
printf( "SCHED_FIFO %d\n", threadno );
break;
default:
printf( "UNKNOWN\n");
}
}
void* thread( void *arg )
{
int i, j;
long threadno = (long)arg;
printf( "thread %d start\n", threadno );
sleep(1);
show_thread_policy( threadno );
for( i = 0; i < 10; ++i ) {
for( j = 0; j < 100000000; ++j ){}
printf( "thread %d\n", threadno );
}
printf( "thread %d exit\n", threadno );
return NULL;
}
int main( int argc, char *argv[] )
{
long i;
pthread_attr_t attr[THREAD_COUNT];
pthread_t pth[THREAD_COUNT];
struct sched_param param;
for( i = 0; i < THREAD_COUNT; ++i )
pthread_attr_init( &attr[i] );
for( i = 0; i < THREAD_COUNT / 2; ++i ) {
param.sched_priority = 10;
pthread_attr_setschedpolicy( &attr[i], SCHED_FIFO );
pthread_attr_setschedparam( &attr[i], ¶m );
pthread_attr_setinheritsched( &attr[i], PTHREAD_EXPLICIT_SCHED );
}
for( i = THREAD_COUNT / 2; i < THREAD_COUNT; ++i ) {
param.sched_priority = 20;
pthread_attr_setschedpolicy( &attr[i], SCHED_FIFO );
pthread_attr_setschedparam( &attr[i], ¶m );
pthread_attr_setinheritsched( &attr[i], PTHREAD_EXPLICIT_SCHED );
}
for( i = 0; i < THREAD_COUNT; ++i )
pthread_create( &pth[i], &attr[i], thread, (void*)i );
for( i = 0; i < THREAD_COUNT; ++i )
pthread_join( pth[i], NULL );
for( i = 0; i < THREAD_COUNT; ++i )
pthread_attr_destroy( &attr[i] );
return 0;
}
3.4 堆栈大小属性
从前面的这些例子中可以了解到,线程的主函数与程序的主函数main()有一个很相似的特性,那就是可以拥有局部变量。虽然同一个进程的线程之间是共享内存空间的,但是它的局部变量确并不共享。原因就是局部变量存储在堆栈中,而不同的线程拥有不同的堆栈。Linux系统为每个线程默认分配了8MB的堆栈空间,如果觉得这个空间不够用,可以通过修改线程的堆栈大小属性进行扩容。
修改线程堆栈大小属性的接口是pthread_attr_setstacksize(),它的完整定义为:
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
它的第二个参数就是堆栈大小了,以字节为单位。需要注意的是,线程堆栈不能小于16KB,而且尽量按4KB(32位系统)或2MB(64位系统)的整数倍分配,也就是内存页面大小的整数倍。此外,修改线程堆栈大小是有风险的,如果你不清楚你在做什么,最好别动它(其实我很后悔把这么危险的东西告诉了你:)。
转载于:https://blog.csdn.net/jiajun2001/article/details/12624923
上一篇: ngxin做邮件代理
下一篇: 获取当前小时的前几个小时(类别)
推荐阅读
-
Linux应用- 使用nscd作为本地dns缓存
-
【Linux应用】pthread详解
-
基于mysql时间处理函数的应用详解
-
Linux部署Java应用Too many open files解决方案 JavaLinuxSUNSecuritythread
-
Linux部署Java应用Too many open files解决方案 JavaLinuxSUNSecuritythread
-
2007年,谁会心慌意乱? SOAIT厂商企业应用虚拟机Linux
-
Python算法应用实战之队列详解
-
Python算法应用实战之栈详解
-
Django应用程序中如何发送电子邮件详解
-
详解Metrics应用监控指标的使用说明