文章目录
- 示例程序
- 线程的合并与分离
- 线程的合并
- 线程的分离
- 线程的属性
- 绑定属性
- 分离属性
- 调度属性
- 算法
- 轮询
- 先进先出
- 其它
- 优先级
- 继承权
- 堆栈大小属性
- 满占警戒区大小属性
- 引用头文件:#include <pthread.h>
- 编译链接:gcc your_program.o -o your_program -lpthread
参数解析:
- pthread_t *thread: 创建后返回的线程句柄;
- pthread_attr_t* attr: 线程属性,可选。NULL;
- *(*start_routine)(void*): 线程入口函数,通过pthread_join() 接口获得函数返回值;
- *arg: 线程入口函数的参数;
返回值:如果线程调用成功就会返回 0。
示例程序
void* thread(void* arg)
{
printf("This is a thread and arg = %d.\n", *(int*)arg);
*(int*)arg = 0;
return arg;
}
int main(int argc, char* argv[])
{
pthread_t th;
int ret;
int arg = 10;
int* thread_ret = NULL;
ret = pthread_create(&th, NULL, thread, &arg); // 创建了一个新的线程,线程的入口函数是 thread,传入了一个为 10 的参数。
if(ret != 0)
{
printf("Create thread error!\n");
return -1;
}
printf("This is the main process.\n");
pthread_join(th, (void**)&thread_ret); // 第一个参数是创建线程的句柄,第二个参数会接受线程的返回值。该函数会阻塞主进程的执行,直到合并的线程执行结束。
printf("thread_ret = %d.\n", *thread_ret);
return 0;
}
由于是多线程程序,在不同环境下会有输出的出入。
线程的合并与分离
线程的合并
通过 pthread_create() 创建了一个线程,对于该系统资源,需要进行资源回收——线程合并(pthread_join()),会阻塞调用进程或线程,直到被合并的线程结束为止。当被合并的线程结束时,pthread_join() 会回收这个线程的资源,并将这个线程的返回值返回。
线程的分离
pthread_detach(),将线程资源的回收工作交由系统自动完成,当被分离的线程结束后,系统会自动回收它的资源。程序无法获得分离线程的返回值。pthread_detach(th);
线程的属性
线程的属性由一个线程属性对象来描述,由 pthread_attr_init() 接口初始化,并由 pthread_attr_destory() 来销毁。
int pthread_attr_init(pthread_attr_t* attr);int pthread_attr_destory(pthread_attr_t* attr);
绑定属性
轻进程(LWP)属于内核的调度实体,一个轻进程可以控制一个或多个线程。与普通任务相比, LWP 与其他进程共享所有(或大部分)的逻辑地址空间和系统资源,与线程相比,LWP 有它自己的进程标识符,并和其它进程有着父子关系。
线程既可以由应用程序管理,又可以由内核管理,而 LWP 仅由内核管理,并向普通进程一样被调度,就和 Linux 系统的内核线程一样。
默认情况下,对于一个拥有 n 个线程的进程,启动多少个轻进程,由哪些轻进程来控制哪些线程由操作系统决定(非绑定),如果要指定某个线程“绑”在某个轻进程上,就可以成为绑定。
绑定属性应用场景:被绑定的线程具有较高的响应速度,因为操作系统的调度主体是轻进程,绑定属性可以保证该线程在需要的时候总有一个轻进程可用。
设定绑定属性:int pthread_attr_setscope(pthread_attr_t* attr, int scope); 第一个参数线程属性对象的指针,第二个参数绑定类型;
绑定类型:
- PTHREAD_SCOPE_SYSTEM 绑定的;
- PTHREAD_SCOPE_PROCESS 非绑定的;Linux 的线程永远都是绑定的,所以PTHREAD_SCOPE_PROCESS在Linux中不管用,一切都是兼容的需要。
...
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);
...
}
分离属性
让线程在创建之前就确定是分离的,可以不再调用 pthread_join() 或者 pthread_detach() 来回收线程资源。
设置接口 pthread_attr_setdetachstat(pthread_attr_t* attr, int detachstate);
detachstate:
- PTHREAD_CREATE_DETACHED: 分离的;
- PTHREAD_CREATE_JOINABLE: 可合并的,也是默认属性;
...
int main(int argc, char *argv[])
{
pthread_attr_t attr;
pthread_t th;
...
pthread_attr_init(&attr);
pthread_attr_setdetachstat(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&th, &attr, thread, NULL);
...
}
调度属性
算法
设置调度算法的接口:pthread_attr_setschedpolicy(pthread_attr_t* attr, int policy);
policy:
- SCHED_RR:
- SCHED_FIFO:
- SCHED_OTHER:
轮询
POSIX 规定。实时调度算法。时间片轮转,当线程时间片用完时,系统重新分配时间片,并将它放置在就绪队列尾部,保证相同优先级的轮询任务获得公平的 CPU 占用时间。
先进先出
POSIX 规定。实时调度算法。一旦线程CPU则一直运行,直到有更高优先级的线程出现或者自己放弃。
其它
Linux 默认。
优先级
Linux 的线程和进程的优先级不一样,线程优先级是从 1~99 的数值,越大优先级越高,仅仅对 SCHED_RR、SCHED_FIFO 有用,SCHED_OTHER 优先级恒为 0。
设置方式:
struct sched_param{int sched_priority; // 线程的优先级
}
int pthread_attr_setschedparam(pthread_attr_t* attr, struct sched_param* param);
进程必须要以root方式运行,此外,还要放弃线程的继承权。
继承权
继承权就是当创建新的线程时,新线程要继承父线程(创建线程)的调度属性,如果不希望新线程继承父线程的调度属性,就要放弃继承权。新线程默认情况下拥有继承权。
int pthread_attr_setinheritsched(pthread_attr_t* attr, int inheritsched);
- PTHREAD_INHERIT_SCHED: 拥有继承权;
- PTHREAD_EXPLICIT_SCHED: 放弃继承权;
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 ) // 一共运行12次,每次创建12个线程
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 ); // 循环创建12个线程
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;
}
堆栈大小属性
线程的入口函数同样需要保存局部变量,线程间的局部变量不共享,因为不同的线程拥有不同的堆栈,Linux 为每个线程默认分配了 8MB 堆栈空间,可以修改此值。
修改堆栈空间接口:int pthread_attr_setstacksize(pthread_attr_t* attr, size_t stacksize);,线程堆栈不能小于 16KB,尽量按照 4KB(32-bit)、2MB(64-biT) 的整数倍分配,
满占警戒区大小属性
Linux 为线程堆栈设置了一个满栈警戒区。这个区域一般就是一个页面,属于线程堆栈的一个扩展区域。一旦有代码访问了这个区域,就会发出 SIGSEGV 信号进行通知。
通过白白浪费空间的方式来保证安全,可以关闭这个警戒区。
int pthread_attr_setguardsize(pthread_attr_t* attr, size_t guardsize); guard_size 以字节为单位来设置警戒区大小,为 0 时关闭禁戒区。当修改了线程堆栈的大小,就一定要同时修改警戒区。
线程之间可以共享地址空间,线程之间的数据交换可以非常快捷,这是线程最显著的优点。在很多时候使用多线程的目的并不是为了对共享数据进行并行处理,更多的是充分利用 CPU 资源而进行并行计算,大多数情况下每个线程只会关心自己的数据而不需要与别人同步。
线程的同步:
- 互斥锁;
- 信号量;