目录
一、线程概念
1 线程和进程的关系
- 轻量级进程(light-weight process),也有PCB,创建线程使用的底层函数和进程一样,都是clone
- 从内核里看进程和线程是一样的,都有各自不同的PCB,但是同一进程的各个线程PCB中指向内存资源的三级页表是相同的
- 进程可以蜕变成线程
- 在美国人眼里,线程就是寄存器和栈
- 在linux下,线程最是小的执行单位;进程是最小的分配资源单位
查看进程的LWP(线程号)
ps -Lf pid
ps -eLf
2 线程间共享资源
- 文件描述符表
- 每种信号的处理方式(减少多线程程序中使用信号)
- 当前工作目录
- 用户ID和组ID
- 内存地址空间(.text/.data/.bss/heap/共享库)
3 线程非共享资源
- 线程id
- 处理器现场和栈指针(内核栈)
- 独立的栈空间(用户空间栈)
- errno变量
- 信号屏蔽字
- 调度优先级
4 优缺点
优点:
- 提高程序的并发性
- 开销小,不用重新分配内存
- 通信和共享数据方便
缺点:
- 线程不稳定(库函数实现)
- 线程调试比较困难(gdb支持不好)
- 对信号支持不好
二、进程控制原语
1 pthread_create函数
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
描述:
创建一个线程
参数:
thread 传出参数,获得当前创建的线程的唯一标识
attr 线程属性设置,如使用默认属性,则传NULL
start_routine 函数指针,指向新线程应该加载执行的函数模块
arg 指定线程将要加载调用的那个函数的参数
返回:
如果成功,pthread_create()返回0;发生错误时,它返回一个错误号,*thread的内容是未定义的
2 pthread_self函数
#include <pthread.h>
pthread_t pthread_self(void);
描述:
这个函数总是成功的,返回调用线程的ID。
注意:
- 线程 ID为pthread_t类型,在 Linux 下为无符号整数(%lu),其他系统中可能是结构体实现。
- 线程ID是进程内部,识别标志(两个进程间,线程ID允许相同)。在进程间应该用LWP号来区别不同的线程。
- 不应使用全局变量pthread_t tid,在子线程中通过 pthread_create传出参数来获取线程ID,而应使用pthread_self。
示例代码 :
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <error.h>
#include <stdlib.h>//exit
#include <string.h>//stderr
void * tfn(void *arg)
{
printf("tfn -- pid = %d, tid = %lu\n",getpid(),pthread_self());
return (void*)0;
}
int main()
{
pthread_t tid;
printf("main -- pid = %d, tid = %lu\n",getpid(),pthread_self());
int ret = pthread_create(&tid,NULL,tfn,NULL);
if(ret!=0)
{
fprintf(stderr,"pthread create error: %s\n",strerror(ret));
exit(1);
}
sleep(1);
return 0;
}
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <error.h>
#include <stdlib.h>//exit
#include <string.h>//stderr
void * tfn(void *arg)
{
int i = (int) arg;
printf("#%d tfn -- pid = %d, tid = %lu\n",i,getpid(),pthread_self());
return (void*)0;
}
int main()
{
pthread_t tid;
printf("main -- pid = %d, tid = %lu\n",getpid(),pthread_self());
int i;
for(i=0;i<5;i++)
{
int ret = pthread_create(&tid,NULL,tfn,(void *)i);
if(ret!=0)
{
fprintf(stderr,"pthread create error: %s\n",strerror(ret));
exit(1);
}
}
return 0;
}
3 pthread_exit函数
在多进程程序中,可以使用exit退出。而在多线程下,使用exit会将整个进程退出,导致一些没有运行完毕的线程都结束。即使使用return只会退出当前的函数,也无法正确退出。因此,在多线程中,应该使用pthread_exit函数。在之前的代码示例中,main函数退出时,会退出整个进程,因此,未执行完毕的线程也都退出。因此,应该使用pthread_exit退出主线程,这样子线程将会继续执行完毕。
#include <pthread.h>
void pthread_exit(void *retval);
4 pthread_join函数
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);//回收线程,retval为线程的返回值。成功返回0,失败返回-1。
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
typedef struct{
int var;
char str[64];
} exit_t;
void * tfn(void *arg)
{
exit_t * ret;
ret = malloc(sizeof(exit_t));
ret->var = 77;
strcpy(ret->str,"hello world");
return (void*) ret;
}
int main()
{
pthread_t tid;
exit_t * ret;
pthread_create(&tid,NULL,tfn,NULL);
pthread_join(tid,(void**)&ret);//回收线程
printf("pthread var = %d, str = %s\n",ret->var,ret->str);
return 0;
}
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
typedef struct{
int var;
char str[64];
} exit_t;
void * tfn(void *arg)
{
exit_t * ret;
ret = malloc(sizeof(exit_t));
ret->var = (int) arg;
strcpy(ret->str,"hello world");
return (void*) ret;
}
int main()
{
pthread_t tid[5];
exit_t * ret[5];
int i;
for(i=0;i<5;i++)
{
pthread_create(&tid[i],NULL,tfn,(void *) i);
}
for(i=0;i<5;i++)
{
pthread_join(tid[i],(void**)&ret[i]);//回收线程,回收完毕才会往下执行
printf("pthread var = %d, str = %s\n",ret[i]->var,ret[i]->str);
}
return 0;
}
5 pthread_detach函数
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
void * tfn(void *arg)
{
int n = 5;
while(n--)
{
printf("pthread tfn n = %d\n",n);
sleep(1);
}
return (void*) 7;
}
int main()
{
pthread_t tid;
void * ret;
pthread_create(&tid,NULL,tfn,NULL);
pthread_detach(tid);//将线程分离出来,不需要父进程回收
int retval = pthread_join(tid,(void**) &ret);
if(retval != 0)//由于子线程已经分离出来了。因此,会报错
{
fprintf(stderr,"pthread_join error %s\n", strerror(retval));
}
else
{
printf("pthread exit with %d\n",(int)ret);
}
return 0;
}
6 pthread_cancel函数
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
void * tfn1(void *arg)
{
printf("thread 1 exiting\n");
return (void*) 111;
}
void * tfn2(void *arg)
{
printf("thread 2 exiting\n");
return (void*) 222;
}
void * tfn3(void *arg)
{
while(1)
{
printf("thread 3. I'm going to die...\n");
sleep(1);
}
}
int main()
{
pthread_t tid;
void * ret;
int i;
pthread_create(&tid,NULL,tfn1,NULL);
pthread_join(tid,&ret);
printf("thread 1 exit code = %d\n",(int)ret);
pthread_create(&tid,NULL,tfn2,NULL);
pthread_join(tid,&ret);
printf("thread 2 exit code = %d\n",(int)ret);
pthread_create(&tid,NULL,tfn3,NULL);
sleep(3);
//杀死子线程,需要线程有取消点(系统调用等),由于tfn3中有printf,该函数会调用write,
//所以可以取消,如果去掉有系统调用的逻辑,该线程不会被取消
pthread_cancel(tid);
pthread_join(tid,&ret);
printf("thread 3 exit code = %d\n",(int)ret);//线程3由于已经被杀死了,ret此时为-1
return 0;
}
7 pthread_equal函数
int pthread_equal(pthread_t t1, pthread_t t2);//比较两个线程是否相等
三、线程属性
typedef struct
{
int etachstate; //线程的分离状态,可以实现与pthread_detach一样的功能
int schedpolicy; //线程调度策略
structsched_param schedparam; //线程的调度参数
int inheritsched; //线程的继承性
int scope; //线程的作用域
size_t guardsize; //线程栈末尾的警戒缓冲区大小
int stackaddr_set; //线程的栈设置
void* stackaddr; //线程栈的位置。栈的低地址
size_t stacksize; //线程栈的大小。默认为每一个线程平均分配栈空间(8M)
}pthread_attr_t;
1 线程属性初始化
属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_initI这个函数必须在pthread_create函数之前调用。之后须用pthread_attr_destroy函数来释放资源。
线程属性主要包括如下属性:作用域(scope)、栈尺寸(stack size)、栈地址(stack address)、优先级(priority)、分离的状态(detached state)、调度策略和参数(scheduling policy and parameters)。默认的属性为非绑定、非分离、缺省的堆栈、与父进程同样级别的优先级。
#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
2 线程的分离状态
#include <pthread.h>
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
参数:
attr 已初始化的线程属性
detachstate PTHREAD_CREATE_DETACHED(分离线程)
PTHREAD_CREATE_JOINABLE(非分离线程)
代码示例:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h> //%s
void * tfn(void * arg)
{
while(1)
{
printf("thread - tfn - ");
sleep(1);
}
}
int main()
{
pthread_t tid;
void * tret;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
pthread_create(&tid,&attr,tfn,NULL);
pthread_attr_destroy(&attr);
int ret;
while(1)
{
ret = pthread_join(tid,(void **)&tret);
if(ret !=0 )
{
fprintf(stderr,"thread %s\n", strerror(ret));
}
else
{
printf("thread - main -\n");
}
sleep(1);
}
return 0;
}
3 线程的栈空间
#include <pthread.h>
int pthread_attr_setstack(pthread_attr_t *attr,
void *stackaddr, size_t stacksize);
int pthread_attr_getstack(const pthread_attr_t *attr,
void **stackaddr, size_t *stacksize);
参数:
attr 指向一个线程属性的指针
stackaddr 返回获取的栈地址
stacksize 返回获取的栈大小
代码示例:
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#define SIZE 0x10000
int print_ntimes(char *str)
{
sleep(1);
printf("%s\n", str);
return 0;
}
void *th_fun(void *arg)
{
int n = 3;
while (n--) print_ntimes("hello th_fun\n");
}
int main(void)
{
pthread_t tid;
int err, detachstate, i = 1;
pthread_attr_t attr;
size_t stacksize;
void *stackaddr;
pthread_attr_init(&attr);
pthread_attr_getstack(&attr, &stackaddr, &stacksize);
printf("stackadd=%p\n", stackaddr);
printf("stacksize=%x\n", (int)stacksize);
pthread_attr_getdetachstate(&attr, &detachstate);
if (detachstate == PTHREAD_CREATE_DETACHED)
printf("thread detached\n");
else if (detachstate == PTHREAD_CREATE_JOINABLE)
printf("thread join\n");
else
printf("thread un known\n");
/* 设置线程分离属性*/
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
while (1) {
/* 在堆上申请内存,指定线程栈的起始地址和大小*/
stackaddr = malloc(SIZE);
if (stackaddr == NULL) {
perror("malloc");
exit(1);
}
stacksize = SIZE;
pthread_attr_setstack(&attr, stackaddr, stacksize);
err = pthread_create(&tid, &attr, th_fun, NULL);
if (err != 0) {
printf("%s\n", strerror(err));
exit(1);
}
printf("%d\n", i++);
}
pthread_attr_destroy(&attr);
return 0;
}
四、NPTL
查看当前pthread库版本 getconf GNU_LIBPTHREADED_VERSION
五、线程使用注意事项
1、主线程退出其他线程不退出,主线程应调用pthread_exit
2、避免僵尸线程。
pthread_join
pthread_detach
pthread_create指定分离属性,
3、被join线程可能在join函数返回前就释放完自己的所有内存资源,所以不应当返回被回收线程栈中的值; malloc和mmap申请的内存可以被其他线程释放。
4、应避免在多线程模型中调用fork,除非马上 exec,子进程中只有调用fork 的线程存在,其他线程在子进程中均pthread_exit
5、信号的复杂语义很难和多线程共存,应避免在多线程引入信号机制。