Linux的多线程发展历史
在早期的LINUX内核中,并不存在真正意义上的线程。当时Linux中常用的线程pthread实际上是通过进程来模拟的,也就是同过fork来创建“轻”进程,并且这种轻进程的线程也有个数的限制,最多只能有4096和此类线程同时运行。2.4内核消除了个数上的限制,并且允许在系统运行中动态的调整进程数的上限,当时采用的是Linux Thread 线程库,它对应的线程模型是“一对一”,而线程的管理是在内核为的函数库中实现,这种线程得到了广泛的应用。但是它不与POSIX兼容。另外还有许多诸如信号处理,进程ID等方面的问题没有完全解决。
在的2.6内核中,进程调度通过重新的编写,删除了以前版本中的效率不高的算法,内核框架页也被重新编写。
开始使用NPTL(Native POSIX Thread Library)线程库,这个线程库有以下几个目标:
- POSIX兼容
- 低启动开销
- 低链接开销
- 与Linux Thread应用的二进制兼容,软硬件的可扩展能力,与C++集成等。
这一切使得2.6的内核多线程机制更加完备。从内核2.6内核开始linux开启进入多线程时代
线程的基本操作
创建
#include <pthread.h>
int pthread_create(
pthread_t *restrict tidp,
const pthread_attr_t *restrict attr,
void *(*start_rtn)(void *), void *restrict arg);
参数
- pthread_t *restrict tidp:创建的的线程ID号
- const pthread_attr_t *restrict attr:指定线程的Attributes,若使用默认值可以设置为NULL
- void (*start_rtn)(void ):线程运行时的任务(函数)指针
- void *restrict arg:运行start_rtn指针指向的函数时,传入的参数
pthread_create出错的时候不会设置errno,而是直接通过返回值返回错误代码
属性获取与设置
id获取
pthread_t pthread_self(void);
pthread_self可以获取线程id号,而通过getpid获取的是当前进程的pid。
pthread_attr_init属性
typedef struct
{
int detachstate; 线程的分离状态
int schedpolicy; 线程调度策略
structsched_param schedparam; 线程的调度参数
int inheritsched; 线程的继承性
int scope; 线程的作用域
size_t guardsize; 线程栈末尾的警戒缓冲区大小
int stackaddr_set;
void* stackaddr; 线程栈的位置
size_t stacksize; 线程栈的大小
}pthread_attr_t;
pthread_attr_init属性对象主要包括是否绑定、是否分离、堆栈大小、优先级。默认的属性为非绑定、非分离、缺省的堆栈、与父进程同样级别的优先级。
- 绑定:关于线程的绑定,牵涉到另外一个概念:轻进程(LWP:Light Weight Process)。轻进程可以理解为内核线程,它位于用户层和系统层之间。系统对线程资源的分配、对线程的控制是通过轻进程来实现的,一个轻进程可以控制一个或多个线程。 默认状况下,启动多少轻进程、哪些轻进程来控制哪些线程是由系统来控制的,这种状况即称为非绑定的。绑定状况下,则顾名思义,即某个线程固定的”绑”在一个轻进程之上。被绑定的线程具有较高的响应速度,这是因为CPU时间片的调度是面向轻进程的,绑定的线程可以保证在需要的时候它总有一个轻进程可用。通过设置被绑定的轻进程的优先级和调度级可以使得绑定的线程满足诸如实时反应之类的要求。pthread_attr_setscope可以设定线程的绑定状态
/*设置线程绑定状态的函数为 pthread_attr_setscope,它有两个参数,
第一个是指向属性结构的指针,第二个是绑定类型,它有两个取值: PTHREAD_SCOPE_SYSTEM(绑定的):表示与系统中所有线程一起竞争CPU时间 ,
PTHREAD_SCOPE_PROCESS(非绑定的):表示仅与同进程中的线程竞争CPU*/
pthread_attr_t attr;
pthread_t tid;
/*初始化属性值,均设为默认值*/
pthread_attr_init(&attr);
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
pthread_create(&tid, &attr, (void *) my_function, NULL);
//获取当前的模式
int pthread_attr_getscope(const pthread_attr_t *attr,
int *contentionscope);
- 分离模式设置:线程分为分离模式和非分离模式,顾名思义在分离模式下,当线程退出时,所有的资源都会自动释放。非分离的线程A终止时,其线程ID和退出状态将保留,直到另外一个线程调用 pthread_join(A)。设置线程分离状态的函数为 pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)第二个参数可选为PTHREAD_CREATE_DETACHED(分离线程)和 PTHREAD _CREATE_JOINABLE(非分离线程)
pthread_attr_t attr;
pthread_t tid;
/*初始化属性值,均设为默认值*/
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, (void *) my_function, NULL);
//也可以直接调用pthread_detach完成分离模式的设置
int pthread_detach(pthread_t tid);
//获取当前的模式
int pthread_attr_getdetachstate(const pthread_attr_t *attr,
int *detachstate)
/*值得注意的是:
如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在pthread_create函数返回之前就终止了,
它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的线程号。
要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用pthread_cond_timewait函数,
让这个线程等待一会儿,留出足够的时间让函数pthread_create返回。设置一段等待时间,是在多线程编程里常用的方法。
*/
- 堆栈设置:默认栈的大小为8M,若直接在栈上分配一个较大的缓存char p[1024*1024*8];毫无疑问一定会提示段错误。可以通过pthread_attr_setstacksize设置栈的大小。
pthread_attr_t attr;
int size;
//获取当前栈的大小
pthread_attr_init(&attr);
pthread_attr_getstacksize(&attr, &size);
//设置栈的大小
pthread_attr_setstacksize(&attr, size)
- 调度策略:通过pthread_attr_getschedpolicy和pthread_attr_setschedpolicy获取和设置调度策略。调度策略可能的值是先进先出(SCHED_FIFO)、轮转法(SCHED_RR),或其它(SCHED_OTHER)。
int pthread_attr_getschedpolicy(const pthread_attr_t*attr,int *policy);
int pthread_attr_setschedpolicy(pthread_attr_t *attr,intpolicy);
- 优先级:通过pthread_attr_getschedparam 和pthread_attr_setschedparam分别用来设置和得到线程的调度参数(优先级)
//函数原型
int pthread_attr_getschedparam(const pthread_attr_t*attr,struct sched_param *param);
int pthread_attr_setschedparam(pthread_attr_t *attr,conststruct sched_param *param);
struct sched_param
{
intsched_priority;
};
#include <pthread.h>
#include <sched.h>
pthread_attr_t attr; pthread_t tid;
sched_param param;
int newprio=20;
/*初始化属性*/
pthread_attr_init(&attr);
/*设置优先级*/
pthread_attr_getschedparam(&attr, ¶m);
param.sched_priority=newprio;
pthread_attr_setschedparam(&attr, ¶m);
pthread_create(&tid, &attr, (void *)myfunction, myarg);
- 销毁pthread_attr_t
int pthread_attr_destroy(pthread_attr_t*attr);
要记得用完后一定要destroy.
POSIX 标准要求:
When a thread attributes object is no longer required,
it should be destroyed using the pthread_attr_destroy() function.
Destroying a thread attributes object has no effect on threads that were created using that object.
退出
- 可以通过return直接退出
- 可以通过调用pthread_exit退出
- 可以通过其他的线程调用void pthread_cancel(pthread_t tid)通知退出
若线程A调用pthread_join(B, &rval_ptr)后被阻塞,直到B退出。若A需要直到B 的退出代码,则需要调用phtread_exit(rval_ptr)退出,但rval_ptr指向的内存的生命周期,不应该指向B的Stack中的数据。
pthread_cleanup_push 与pthread_cleanup_pop通常配合pthread_cancel使用,完成后续的清理工作!
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
static void pthread_func_1 (void *);
static void pthread_func_2 (void *);
static void * pthread_func_3(void * );
static void clean_fun1(void * );
static void clean_fun2(void * );
/*
线程1:pthread_func_1 游离模式,主动调用pthread_exit退出
线程2:pthread_func_2 非游离模式,通过return退出。
线程3:pthread_func_3 非游离模式,主线程调用pthread_cancel 使其退出,配合pthread_cleanup_push 与pthread_cleanup_pop 做后续的清理工作!
*/
int main (int argc, char** argv)
{
pthread_t pt_1 = 0;
pthread_t pt_2 = 0;
pthread_t pt_3 = 0;
pthread_attr_t atrr = {0};
int ret = 0;
printf("threat test!\n");
/*初始化属性线程属性*/
pthread_attr_init (&atrr);
pthread_attr_setscope (&atrr, PTHREAD_SCOPE_SYSTEM);
pthread_attr_setdetachstate (&atrr, PTHREAD_CREATE_DETACHED);
ret = pthread_create (&pt_1, &atrr, pthread_func_1, NULL);
if (ret != 0)
{
perror ("pthread_1_create");
}
ret = pthread_create (&pt_2, NULL, pthread_func_2, NULL);
if (ret != 0)
{
perror ("pthread_2_create");
}
ret = pthread_create (&pt_3, NULL, pthread_func_3, NULL);
if (ret != 0)
{
perror ("pthread_3_create");
}
printf("waiting for pthread_2 exiting!\n");
pthread_join (pt_2, NULL);
printf("pthread_2 has exited!\n");
printf("begin to cancel pthread_3!\n");
sleep(2);
ret=pthread_cancel(pt_3);
if(ret!=0)
{
perror("cancel error:");
exit(0);
}
printf("waiting for pthread_3 exiting!\n");
pthread_join (pt_3, NULL);
printf("pthread_3 has exited!\n");
return 0;
}
static void pthread_func_1 (void * para)
{
int i = 0;
for (; i < 6; i++)
{
printf ("This is pthread_1.\n");
if (i == 2)
{
pthread_exit (0);
}
}
return;
}
static void pthread_func_2 (void * para)
{
int i = 0;
for (; i < 4; i ++)
{
sleep(1);
printf ("This is pthread_2.\n");
}
printf ("pthread_2 will be exit!\n");
return;
}
static void * pthread_func_3(void * arg)
{
int i=0;
pthread_cleanup_push(clean_fun1,NULL);
pthread_cleanup_push(clean_fun2,NULL);
for(i=0 ; i<100 ; i++)
{
sleep(1);
printf ("This is pthread_3.\n");
}
//这里要注意,如果将sleep(100);换成while(1);的话,程序会一直暂停.push和pop要成对出现.
//因为while(1);运行的太快,线程不接受cancel信号
//while(1);
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
return NULL;
}
static void clean_fun1(void * arg)
{
printf("this is clean fun1\n");
}
static void clean_fun2(void * arg)
{
printf("this is clean fun2\n");
}
gcc -g -pthread threadTest.c -lpthread -o test 生成测试代码