Linux系统应用编程---线程原语

2019-07-14 11:48发布

线程概念

什么是线程 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终止自己