Linux多线程介绍
多线程是在同一时间需要完成多项任务的时候实现的。
- 轻量级进程(light-weight process),每个线程也有自己与进程控制表和 PCB 相似的线程控制表 TCB ,而这个TCB 中所保存的线程状态信息则要比 PCB 表少得多,这些信息主要是相关栈指针(系统栈和用户栈),寄存器中的状态数据。
- 从内核里看进程和线程是一样的,都有各自不同的PCB或者TCB,但是PCB或者TCB中指向内存资源的三级页表
是相同的。
- 进程可以蜕变成线程。
- 在linux下,线程最是小的执行单位;进程是最小的分配资源单位。
线程间共享资源
线程间非共享资源
线程优点
线程缺点
man帮助
查看manpage关于pthread的函数
安装pthread相关manpage
线程的创建与使用
创建线程
用pthread_create()函数,其函数原型是:
第一个参数为指向线程标识符的指针,第二个参数用到设置线程属性,分配多少的栈空间,以及运行优先级,大多数情况下,我们不需要设置线程的属性,那么空指针NULL就可以了,第三个参数是线程运行函数的起始地址,最后一个参数是传递给线程的参数。
当创建线程成功时,函数返回0,失败返回错误码。
不会设置errno。
在一个线程中调用pthread_create()创建新的线程后,当前线程从pthread_create()返回继续往下执行,而新的线程所执行的代码由我们传给pthread_create的函数指针start_routine决定。
start_routine函数接收一个参数,是通过pthread_create的arg参数传递给它的,该参数的类型为void *,这个指针按什么类型解释由调用者自己定义。start_routine的返回值类型也是void *,这个指针的含义同样由调用者自己定义。
start_routine返回时,这个线程就退出了,其它线程可以调用pthread_join得到start_routine的返回值,类似于父进程调用wait(2)得到子进程的退出状态。
pthread_create成功返回后,新创建的线程的id被填写到thread参数所指向的内存单元。我们知道进程id的类型是pid_t,每个进程的id在整个系统中是唯一的,调用getpid(2)可以获得当前进程的id,是一个正整数值。线程id的类型是thread_t,它只在当前进程中保证是唯一的,在不同的系统中thread_t这个类型有不同的实现,它可能是一个整数值,也可能是一个结构体,也可能是一个地址,所以不能简单地当成整数用printf打印,调pthread_self(3)可以获得当前线程的id。
Linux中多线程的实现可以使用pthread线程模型的编程。pthread线程通过libpthread线程函数库来实现。所以在编译多线程程序的时候,需要链接libpthread,比如
源代码: pthread_test.c
#include
#include
#include
#include
void* mythread(void * arg)
{
int i;
for(i = 0; i < 5; i++)
{
printf("pthread
");
sleep(1);
}
return (void*)0;
}
int main(int argc, char** argv)
{
pthread_t pth;
int i, ret;
ret = pthread_create(&pth, NULL, mythread, NULL);
if(ret != 0)
{
printf("create pthread error
");
exit(1);
}
for(i = 0; i < 5; i++)
{
printf("main process
");
sleep(1);
}
return 0;
}
上面的代码,第二个参数我们设置为空指针,这样将生成默认属性的线程。函数mythread如果不需要参数,那么最后一个参数也可以设置为空指针。
运行结果:
两个线程的输出是交替的。
错误打印
strerror返回的是strerror申请的空间,多次调用strerror,将改变这个值,出现意外的结果,strerror_r让调用者自己申请一个buf来存放。
线程的退出
一种是子线程程序运行完自动退出或者直接return,一种是调用pthread_exit(),其函数原型为:
函数的参数时线程退出时的返回码,只要pthread_join中的第二个参数value_ptr不是NULL,这个值将被传递给主线程。
调用线程退出函数,注意和exit函数的区别,任何线程里exit导致进程退出,其他线程未工作结束,主控线程退出时不能return或exit。 需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
源代码:pthread_test2.c
#include
#include
#include
#include
void* mythread(void * arg)
{
int i;
for(i = 0; i < 5; i++)
{
printf("pthread
");
sleep(1);
}
pthread_exit((void *)i);
}
int main(int argc, char** argv)
{
pthread_t pth;
int i, ret;
void* result;
ret = pthread_create(&pth, NULL, mythread, NULL);
if(ret != 0)
{
printf("create pthread error
");
exit(1);
}
for(i = 0; i < 5; i++)
{
printf("main process
");
sleep(1);
}
pthread_join(pth, &result);
printf("pthread exit code = [%d]
", (int)result);
return 0;
}
运行结果:
线程回收
linux线程执行有两种状态joinable状态和unjoinable状态。
1、如果线程是joinable状态,当线程函数自己返回退出时或pthread_exit时都不会释放线程所占用堆栈和线程描述符。只有当你调用了pthread_join之后这些资源才会被释放。
函数pthread_join(),这个函数是主线程用于等待子线程结束,其函数原型是:
第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。这个函数是一个线程
阻塞的函数,调用该函数将一直等待到被等待的线程结束为止,这函数返回时,被等待线程的资源被回收。
2、若是unjoinable状态的线程,这些资源在线程函数退出时或 pthread_exit 时自动会被释放。
unjoinable属性可以在pthread_create时指定,或在线程创建后在线程中pthread_detach自己, 如:pthread_detach(pthread_self()) ,将状态改为unjoinable状态,确保资源的释放。其实简单的说就是在线程函数头加上 pthread_detach(pthread_self()) 的话,线程状态改变,在函数尾部直接pthread_exit线程就会自动退出。
pthread_t tid : 分离线程tid 返回值:成功返回0,失败返回错误号。
#include
#include
#include
#include
#include
void *my_thread(void *arg)
{
int i = 3;
while (i--)
{
printf("thread count %d
", n);
sleep(1);
}
return (void *)1;
}
int main(void)
{
pthread_t tid;
void *tret;
int err;
pthread_create(&tid, NULL, my_thread, NULL);
pthread_detach(tid);
err = pthread_join(tid, &tret);
if (err != 0)
printf("thread %s
", strerror_r(err));
else
printf("thread exit code %d
", (int)tret);
sleep(1);
return 0;
}
pthread_cancel
在进程内某个线程可以取消另一个线程。
被取消的线程,退出值,定义在Linux的pthread库中常数PTHREAD_CANCELED的值是-1。可以在头文件pthread.h中找到它的定义:
源码:pthread_cancel.c
#include
#include
#include
#include
void *th_func(void *arg)
{
while(1)
{
printf("thread run
");
sleep(1);
}
}
int main(void)
{
pthread_t tid;
void *ret;
pthread_create(&tid, NULL, th_func, NULL);
sleep(3);
pthread_cancel(tid);
pthread_join(tid, &ret);
printf("thread exit code %d
", (int)ret);
return 0;
}
同一进程的线程间,pthread_cancel向另一线程发终止信号。系统并不会马上关闭被取消线程,只有在被取消线程下次系统调用时,才会真正结束线程。或调用pthread_testcancel,让内核去检测是否需要取消当前线程。
pthread_equal
比较两个线程是否相等
线程属性
本节作为指引性介绍,linux下线程的属性是可以根据实际项目需要,进行设置,之前我们讨论的线程都是采用线程的默认属性,默认属性已经可以解决绝大多数开发时遇到的问题。如我们对程序的性能提出更高的要求那么需要设置线程属性,比如可以通过设置线程栈的大小来降低内存的使用,增加最大线程个数。
注:目前线程属性在内核中不是直接这么定义的,抽象太深不宜拿出讲,为方便大家理解,使用早期的线程属性定义,两者之间定义的主要元素差别不大。
线程属性初始化
先初始化线程属性,再pthread_create创建线程。
线程的分离状态
非分离状态:线程的默认属性是
非分离状态,这种情况下,原有的线程
等待创建的线程结束。只有pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。== 分离状态==:分离线程
没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。应该根据自己的需要,选择适当的分离状态。
pthread_attr_t *attr : 被已初始化的线程属性。
int *detachstate : 可选为 PTHREAD_CREATE_DETACHED (分离线程)和 PTHREAD _CREATE_JOINABLE (非分离线
程)。
线程设置分离态有两种方式:第一种是在创建函数前直接在结构体里设置分离态,第二种是创建线程后用函数设置成分离态。我们一般使用第一种更好。因为万一创建线程执行代码很短,设置分离态的函数还没执行完,线程函数可能就已经执行完了。
线程的栈大小(stack size)
当系统中有很多线程时,可能需要减小每个线程栈的默认大小,防止进程的地址空间不够用,当线程调用的函数会分配很大的局部变量或者函数调用层次很深时,可能需要增大线程栈的默认大小。 函数pthread_attr_getstacksize和pthread_attr_setstacksize提供设置。
attr 指向一个线程属性的指针。 stacksize 返回线程的堆栈大小。 返回值:若是成功返回0,否则返回错误的编号。
除上述对栈设置的函数外,还有以下两个函数可以获取和设置线程栈属性,当进程栈地址空间不够用时,指定新建线程使用由malloc分配的空间作为自己的栈空间。通过pthread_attr_setstack和pthread_attr_getstack两个函数分别设置和获取线程的栈地址。传给pthread_attr_setstack函数的地址是缓冲区的低地址(不一定是栈的开始地址,栈可能从高地址往低地址增长)。
attr 指向一个线程属性的指针 stackaddr 返回获取的栈地址 stacksize 返回获取的栈大小 返回值:若是成功返回0,否则返回错误的编号