线程概念
什么是线程
LWP: light wight process,轻量级进程,本质还是进程(在Linux环境下)
进程:独立地址空间,拥有PCB
线程:也有PCB,但没有独立的地址空间(共享)
区别:在于是否共享地址空间
Linux下: 进程:最小的执行单位
线程:最小分配资源单元,可看成是只有一个进程的线程
Linux线程实现原理
1、轻量级线程也有PCB,创建线程使用的底层函数和进程一样,都是clone
2、从内核里看进程和线程是一样的,都有各自不同的PCB,但是PCB中指向内存资源的三级页表示相同的,
3、进程可以蜕变成线程
4、线程可看做寄存器和栈的结合
5、在Linux下,线程是最小的执行单位,进程是最小的资源分配单位。
查看LWP号
ps -lf pid 查看指定进程的lwp号
ps –aux 看进程
ps –eLf 看线程
ps –Lw pid 查看某一个进程的线程有那些
线程共享资源
1、文件描述符表
2、每种信号的处理方式
3、当前工作目录
4、用户ID和组ID
5、内存地址空间(Text/data/bss/堆/共享库)
线程非共享资源
1.线程id
2.处理器现场和栈指针(内核栈)
3.独立的栈空间(用户空间栈)
4.errno变量
5.信号屏蔽字
6.调度优先级
线程优缺点
优点:
提高程序的并发性
开销小,不用重新分配内存
通信和共享数据方便
缺点:
线程不稳定(库函数实现)
线程调试比较困难(gdb支持不好)
线程无法使用unix经典事件,例如信号
线程原语
1、pthread_create
#include
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
pthread_t *thread:传递一个pthread_t变量地址进来,用于保存新线程的tid(线程ID)
const pthread_attr_t *attr:线程属性设置,如使用默认属性,则传NULL
void *(*start_routine) (void *):函数指针,指向新线程应该加载执行的函数模块
void *arg:指定线程将要加载调用的那个函数的参数
返回值:成功返回0,失败返回错误号。以前学过的系统函数都是成功返回0,失败返回-1,而错误号保存在全局变量errno中,而pthread库的函数都是通过返回值返回错误号,虽然每个线程也都有一个errno,但这是为了兼容其它函数接口而提供的,pthread库本身并不使用它,通过返回值返回错误码更加清晰。
在一个线程中调用pthread_create()创建新的线程后,当前线程从pthread_create()返回继续往下执行,而新的线程所执行的代码由我们传给pthread_create的函数指针start_routine决定。
2、pthread_self
#include
pthread_t pthread_self(void);
获取调用线程tid,其作用对应进程中getpid()函数。
线程ID:pthread_t类型,其本质是在Linux下午无符号整数,其他系统中可能是结构体实现
线程ID是进程内部识别标志。(两个进程间,线程ID允许相同)
注意:不应该使用全局变量pthread_t tid,在子进程中通过pthread_create传出参数来获取线程ID,而应该使用pthread_self()
线程ID与线程号的区别:
Ps –eLf命令看到的LWP那一列是线程号,线程号是CPU分配时间轮片的依据
Pthread_self得到的是线程ID,线程ID用来在进程内部区分线程
程序1:循坏创建多个子线程
#include
#include
#include
#include
#include
void *thread_func(void *arg)
{
int i = (int )arg;
sleep(i);
printf("%dth thread: thread id = %lu, pid = %u
", i+1, pthread_self(), getpid());
return NULL;
}
int main(void)
{
pthread_t tid;
int ret, i;
for(i=0; i<5; i++){
ret = pthread_create(&tid, NULL, thread_func, (void *)i);
if(ret != 0){
fprintf(stderr, "pthread_create error:%s
", strerror(ret));
exit(1);
}
}
//sleep(i);
//return 0; //将当前进程退出
pthread_exit(NULL);
}
执行结果如下:
线程默认共享数据段、代码段等地址空间,常用的是全局变量,而进程不共享全局变量,只能借助mmap.
程序2:验证线程之间共享全局数据
#include
#include
#include
#include
#include
int var = 100;
void *tfn(void *arg)
{
var = 200;
printf("thread.
");
return NULL;
}
int main(void)
{
printf("At first var = %d.
", var);
pthread_t tid;
pthread_create(&tid, NULL, tfn, NULL);
sleep(1);
printf("after pthread_create, var = %d.
", var);
return 0; //将当前进程退出
}
执行结果如下:
3、pthread_exit
#include
void pthread_exit(void *retval);
void *retval:线程退出时传递出的参数,可以是退出值或地址,如是地址时,不能是线程内部申请的局部地址。一般传NULL。
pthread_exit与exit的区别:
调用线程退出函数pthread_exit,只是推出当前线程
任何线程里使用exit都会导致进程退出,主控线程退出时不能return或exit,因为会影响其他线程未工作。
exit与_exit的区别:
exit关闭C标准文件流,并刷新文件缓冲区
_exit(Linux底层函数),导致进程退出,关闭未关闭的文件描述符
需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
4、pthread_join
阻塞等待线程退出,获取线程退出状态,其作用对应进程中的waitpid()函数
#include
int pthread_join(pthread_t thread, void **retval);
pthread_t thread:回收线程的tid
void **retval:接收退出线程传递出的返回值
返回值:成功返回0,失败返回错误号
5、pthread_cancel
在进程内的某个线程可以取消另一个线程
#include
int pthread_cancel(pthread_t thread);
6、pthread_detach
#include
int pthread_detach(pthread_t tid);
pthread_t tid:分离线程tid
返回值:成功返回0,失败返回错误号。
一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取它的状态为止。但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL。如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。
7、pthread_equal
比较两个线程是否相等
线程终止方式
如果需要只终止某个线程而不终止整个进程,可以有三种方法:
1.从线程主函数return。这种方法对主控线程不适用,从main函数return相当于调用exit。
2.一个线程可以调用pthread_cancel终止同一进程中的另一个线程。
3.线程可以调用pthread_exit终止自己