Linux线程

1. 线程的概念

Linux内核中线程实现原理

Linux线程实现原理与Windows不同。Linux下线程基于进程实现,线程和进程性能基本一致。Windows下使用线程编程比较多。

LWP:LightWeightProcess 轻量级进程 ->线程 本质仍是线程

进程有独立PCB,独立地址空间

线程有各自PCB(指向的三级页表相同 拷贝的指针指向相同的页目录 物理内存单元实际上共享),共享进程的一块地址空间

当有第一个线程创建,原进程退化为线程。

线程是最小的执行单位,进程是最小分配资源的单位,线程可以看做寄存器和栈的集合(地址 指针 局部变量)

pthread_create/fork底层函数clone,都是从父进程拷贝

内核中不区分进程和线程,以PCB为区分标志。

查看LWP线程号 (不是线程id):ps -Lf pid

LWP:CPU分配时间片的依据

线程ID:在进程内部用于区分线程

线程共享资源

同一进程内的线程共享资源:

文件描述符表、每种信号的处理方式(不建议线程和信号相关问题同时实现)、共享当前工作目录、共享用户id和组id、共享内存地址空间(.text/.data/.bss/heap/共享库 例外:errno变量)

非共享资源:

线程id

处理器现场(寄存器和栈指针)内核栈

独立的栈空间(用户空间栈)

errno

信号屏蔽字

调度优先级

优缺点

优点 :

提高并发性(多个线程相当于进程去抢占时间片)

相对于进程开销小(0~4G)

通信和共享数据容易

缺点:

pthread库函数 不稳定(相对于系统级调用)

调试和编写困难 (gdb无法使用)

对信号支持较弱

线程优点相对突出 但是Linux下进程线程区别不大

2. 线程控制原语

参数详见manpage

注意:编译链接使用-lpthread

pthread_self

返回调用线程的id(pthread_t)

pthread_create

创建线程,成功返回0,错误返回错误编号(和系统调用不同 不再设置errno),使用strerr把错误编号转换成char*

pthread_exit

退出线程(exit是退出整个进程),没有返回值

pthread_join

阻塞等待回收线程,获取退出状态。获取pthread_exit(void)的参数void*

pthread_detach

线程分离(状态上分离),不会产生僵尸线程,不需要join回收。如果回收了,join返回失败。

pthread_cancel

杀死线程,退出值为-1。注意 不是实时的 需要等待线程到达某一个取消点 (比如一个系统调用,如果线程中没有取消点,可以通过调用pthread_testcancel设置取消点)

pthread_testcancel

设置取消点

pthread_equal

比较两个线程id 是否相等

进程和线程的控制原语对比

  1. fork pthread_create
  2. exit(int) pthread_exit(void*)
  3. wait(int*) pthread_join(,void**)
  4. //分离态不能正常回收 返回错误 进程被杀死 回收返回值-1
  5. kill pthread_cancel() //不是实时取消 取消点/检查点
  6. //取消点 系统调用 man 7 pthreads
  7. pthread_testcancel()
  8. getpid pthread_self
  9. pthread_detach //设置分离 优点 自动清理pcb

3. 线程属性控制(扩展)

  1. pthread_arrt_t attr;
  2. pthread_attr_init(pthread_attr_t*);
  3. pthread_attr_destroy(pthread_attr_t*);

能设置分离态(create时自动分离无需手动调用detach)

  1. pthread_attr_setdetachstate(attr*,int state);
  2. //state : PTHREAD_CREATE_DETACH
  3. // PTHREAD_CREATE_JOINABLE
  4. //.....getdetachstate

设置线程栈的大小(进程默认8M,线程栈均分这8M空间)从堆空间申请额外空间补充

  1. pthread_attr_setstack(attr*,void* stackaddr,size_t stacksize);//stackaddr: malloc的空间
  2. //get...

线程栈警戒缓冲区大小(两个线程栈空间之间设置警戒区)

  1. pthread_addr_setstacksize(attr*,size_t stacksize);
  2. //getstacksize.....

4.线程注意事项

线程共享全局变量验证程序(进程不共享)

  1. #include<unistd.h>
  2. #include<string.h>
  3. #include<stdlib.h>
  4. #include<stdio.h>
  5. int share = 0;
  6. void* fun(void* arg)
  7. {
  8. share+=100;
  9. printf("tid:%lu share:%d\n",pthread_self(),share);
  10. return NULL;
  11. }
  12. int main()
  13. {
  14. pthread_t tid;
  15. for(int i=0;i<5;i++)
  16. {
  17. sleep(2);
  18. int ret =pthread_create(&tid,NULL,fun,NULL);
  19. if(ret!=0)
  20. {
  21. printf("err:pthread create %d\n",ret);
  22. printf("thread: %s\n",strerror(ret));
  23. fprintf(stderr,"error:%s\n",strerror(ret));
  24. exit(1);
  25. }
  26. }
  27. return 0;
  28. }

return、exit和pthread_exit的区别

exit退出进程 不能在线程中使用

main函数中使用return 0 ,会导致线程未执行完成时,主控线程结束导致全部结束。

return:返回到调用者

pthread_exit—->嵌套调用函数中也能使用,来结束线程。主线程调用 其他线程不会全部被强制结束。

回收线程

回收子进程只能由父进程,但线程之间彼此也能回收。

设置分离态后可能出现的问题

设置分离态后,假如线程运行非常快,在create函数返回前结束,它的tid可能被其他线程使用,那么调用create的线程就得到了错误的线程号。 避免:采取同步措施:pthread_cond_timedwait

malloc和mmap申请的可以被其他线程释放

应该避免在多线程中fork、exec,线程fork,子进程中只有调用fork的线程存在,其他线程在子进程中均pthread_exit

避免在多线程中使用信号机制

4. NPTL

查看NPTL库的版本:

getconf GNU_LIBPTHREAD_VERSION

编译链接:-lpthread

5. DEMO:多线程拷贝