线程的创建,终止和回收
Posted milaiko
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程的创建,终止和回收相关的知识,希望对你有一定的参考价值。
线程
线程概念
线程:轻量级的进程,其本质仍是进程。(在Linux环境下)
进程: 具有独立的地址空间,拥有pcb
线程:有独立的pcb,但是没有独立的地址空间(共享)
Linux下:
线程:最小的执行单位
进程:最小分配资源单位,可看成只有一个线程的进程。
ps -Lf 去看看firefox
进程控制块 PCB
每个进程在内核中都有一个进程控制块(PCB)来维护进程相关信息,Linux内核的进程控制块是task_struct结构体。
其内部成员有很多,我们大致了解一部分
- 进程的状态,有就绪、运行、挂起、停止等状态
- 进程切换时需要保存和恢复的一些cpu寄存器
- 描述虚拟地址的空间信息
- 描述控制终端的信息
- 当前工作目录
- umask掩码
- 文件描述符表,包含很多指向file结构体的指针
- 和信号相关的信息
- 用户id和组id
- 会话(session)和进程组
- 进程可以使用的资源上限。
实际上,虚拟地址只能与MMU相关,而如何通过MMU相关是通过PCB和物理地址相关。
从内核看线程和进程是一样的, 都有各自不同的PCB,但是PCB中指向 内存资源的三级页表是不同的。
- 轻量级进程,也有PCB,创建线程使用的底层函数和进程一样,都是clone
- 从内核里看进程和线程是一样的,都有各自不同的PCB, 但是PCB指向内存资源的三级页表是不同的
- 进程可以蜕变成线程
- 线程可以看作寄存器和栈的集合
- 在linux下, 线程最小的执行单位是线程,进程是最小的资源分配单位
pthread_self()
#include <pthread.h>
pthread_t pthread_self(void);
这个函数是返回自身的线程ID
在linux使用无符号长整型表示pthread_t数据类型,solaris 把 pthread_t表示为无符号整型,FreeBSD和MAC OS是使用pthread的结构体的指针来表示
pthread_create
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
- thread 创出参数
- pthread_attr_t 线程属性, 如果不想看这个值可以传NULL
- 一个函数指针
- arg,上一个函数指针的参数,如果不需要参数传NULL
- 返回值
- 成功返回0, 失败返回error number,并且content of thread没有定义。
创建线程案例
#include"head.h"
#include<pthread.h>
void *tfn(void *arg){
printf("pthread : pid = %d, tid = %lu\\n", getpid(), pthread_self());
return NULL;
}
int main(){
pthread_t tid;
// tid = pthread_self();
printf("main : pid = %d, tid = %lu\\n", getpid(), pthread_self());
int ret = pthread_create(&tid,NULL, tfn,NULL);
if(ret != 0){
sys_err("pthread create error");
}
sleep(1); //给定足够的时间让线程打印
return 0;
}
主线程传递给子线程的变量要使用值传递
使用值传递
#include"head.h"
// int是4字节,void* 是8字节,因为先是从8字节到4字节,再是4字节到8字节,没有数据丢失
void *tfn(void* arg){
long int i = (long int)arg; //根据编译器,有的编译器不允许从8字节到4字节的强转,以防止数据丢失
sleep(i);
printf("--I'm %ldth thread, :pid = %d, tid = %lu\\n", i+1, getpid(), pthread_self());
return NULL;
}
int main(){
int i;
int ret;
pthread_t tid;
for(i=0;i<5;i++){
ret = pthread_create(&tid, NULL, tfn, (void*)i); //传入参数
if(ret!=0){
sys_err("pthread_create error");
}
}
sleep(i);
return 0;
}
使用指针传递
#include"head.h"
// int是4字节,void* 是8字节,因为先是从8字节到4字节,再是4字节到8字节,没有数据丢失
void *tfn(void* arg){
int i = *((int* )arg); //将之强转为int指针 ,并且取引用。
sleep(i);
printf("--I'm %dth thread, :pid = %d, tid = %lu\\n", i+1, getpid(), pthread_self());
return NULL;
}
int main(){
int i;
int ret;
pthread_t tid;
for(i=0;i<5;i++){
ret = pthread_create(&tid, NULL, tfn, (void*)&i); //传入i变量的地址
if(ret!=0){
sys_err("pthread_create error");
}
}
sleep(i);
return 0;
}
打印出来会发现i并不是老老实实从1到5,而是在1——5之间随机的树,其实聪明的同志已经知道了。
那我们解析一下
这是变量i在栈区的表现,因为是依赖地址去寻找值的。而线程的切换需要是时间,主线程里面对i循环速度会更快,所以当子线程没有获得正确的i值时,i的值已经切换,所以也导致了arg的切换。
嗯?也就是说在for循环sleep一下,就能够得到正确的顺序了?——————u1s1确实,但是实际上不会让你天天sleep把。
主子线程共享全局变量
pthread_exit函数
该函数的功能是退出当前的线程
#include <pthread.h>
void pthread_exit(void *retval);
#include"head.h"
void* func(){
return NULL;
}
void* func1(){
pthread_exit(NULL); //将当前进程退出
return NULL;
}
// int是4字节,void* 是8字节,因为先是从8字节到4字节,再是4字节到8字节,没有数据丢失
void *tfn(void* arg){
long int i = (long int)arg; //但是不允许void* 转化到 int,因为有数据丢数
sleep(i);
if(i == 2){
// exit(0); //表示退出进程
// return NULL; //达到目的, 表示返回函数调用者, 如果return Null是直接在tfn能够成功退出进程
// func(); //但是换了个函数就不能达到目的
func1();
}
printf("--I'm %ldth thread, :pid = %d, tid = %lu\\n", i+1, getpid(), pthread_self());
return NULL;
}
int main(){
int i;
int ret;
pthread_t tid;
for(i=0;i<5;i++){
ret = pthread_create(&tid, NULL, tfn, (void*)i); //传入参数
if(ret!=0){
sys_err("pthread_create error");
}
}
sleep(i);
return 0;
}
pthread_join函数
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
该函数的功能是回收一个终止的线程
#include"head.h"
struct thrd{
int var;
char str[256];
};
void *tfn(void *arg){
struct thrd *tval;
tval = (struct thrd* )malloc(sizeof(tval));
tval->var = 100;
strcpy(tval->str, "hello thread");
return (void*)tval;
}
int main(){
pthread_t tid;
struct thrd *retval;
int ret = pthread_create(&tid, NULL, tfn, NULL);
if(ret != 0){
sys_err("pthread_create error");
}
ret = pthread_join(tid, (void **)&retval); //tid 是要回收的tid, 把tid返回的东西拿到手
if(ret != 0){
sys_err("pthread_join error");
}
printf("child thread exit with var = %d, str=%s\\n", retval->var, retval->str);
pthread_exit(NULL);
}
pthread_cancel
#include <pthread.h>
int pthread_cancel(pthread_t thread);
功能:从主线程杀死子线程
代码案例:
#include"head.h"
void* tfn(void *arg){
while (1)
{
printf("thread: pid = %d, tid =%lu\\n", getpid(), pthread_self());
sleep(1);
}
return NULL;
}
int main(){
pthread_t tid;
int ret = pthread_create(&tid, NULL, tfn, NULL);
if(ret!=0){
fprintf(stderr, "pthread_create error:%s\\n", strerror(ret));
exit(1);
}
printf("main: pid = %d, tid = %lu\\n", getpid(), pthread_self());
sleep(5);
ret = pthread_cancel(tid);
if(ret != 0){
fprintf(stderr, "pthread cancel error:%s\\n", strerror(ret));
exit(1);
}
while(1);
}
注意:如果说子线程没有退出点,那么父线程调用pthread_cancel也无法将其关闭。
pthread_detach函数
功能:实现线程分离
#include <pthread.h>
int pthread_detach(pthread_t thread);
线程detacch和joinable
一个可汇合的线程(joinable)终止时,它的线程ID和退出状态将留存到另一个线程对它调用pthread_join
一个脱离的线程就像守护进程,终止时, 所有相关资源释放。
线程属性—线程分离
线程分离可以使用pthread_detach来实现,但是如果是直接通过设置属性可以在create时就实现线程分离。
- 初始化线程属性
int pthread_attr_init(pthread_attr_t *attr);
//return 成功:0 失败errno
- 销毁线程属性所占用的资源
int pthread_attr_destroy(pthread_attr_t *attr);
- 设置线程属性
#include <pthread.h>
// 设置线程属性 分离或不分离
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
// 获得线程属性
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
detachstate:PTHREAD_CREATE_DETACHDE (分离线程)
detachstate:PTHREAD_CREATE_JOINABLE (可汇合线程)
示例代码
#include"head.h"
#include<pthread.h>
void *tfn(void *arg){
printf("pthread : pid = %d, tid = %lu\\n", getpid(), pthread_self());
return NULL;
}
int main(){
pthread_t tid;
// tid = pthread_self();
pthread_attr_t attr;
int ret = pthread_attr_init(&attr);
if(ret != 0){
sys_err("arr init error", ret);
}
// pthread_attr_destroy();
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
if(ret != 0){
sys_err("attr_setdetachstate error", ret);
}
ret = pthread_create(&tid,NULL, tfn,NULL);
if(ret != 0){
sys_err("pthread create error", ret);
}
// sleep(1); //给定足够的时间让线程打印
ret = pthread_attr_destroy(&attr);
if(ret != 0){
fprintf(stderr, "att_destory error", ret);
}
ret = pthread_join(tid, NULL);
if(ret != 0){
fprintf(stderr, "tid_join error", ret);
}
printf("main : pid = %d, tid = %lu\\n", getpid(), pthread_self());
pthread_exit((void*)0); //这个函数是来退出主线程,而主线程退出不影响子线程,如果说把进程给结束了,才会影响到子线程
return 0;
}
注意事项:
避免在多线程模型中调用fork,除非马上exec,子进程中只有调用fork的线程存在,其他线程在子进程中均pthread_exit
以上是关于线程的创建,终止和回收的主要内容,如果未能解决你的问题,请参考以下文章