自助旅游网站开发分析报告,如何美化wordpress页面,WordPress订阅下载插件,苏宁电器网站建设特点分析了解#xff1a; Linux底层提供创建轻量级进程/进程的接口clone#xff0c;通过选择是否共享资源创建。 vfork和fork都调用的clone进行实现#xff0c;vfork和父进程共享地址空间-轻量级进程。 库函数pthread_create调用的也是底层的clone。 POSIX线程库 与线程有关的函数构… 了解 Linux底层提供创建轻量级进程/进程的接口clone通过选择是否共享资源创建。 vfork和fork都调用的clone进行实现vfork和父进程共享地址空间-轻量级进程。 库函数pthread_create调用的也是底层的clone。 POSIX线程库 与线程有关的函数构成了一个完整的系列绝大多数函数的名字都是以“pthread_”打头的 要使用这些函数库要通过引入头文件pthread.h 链接这些线程函数库时要使用编译器命令的“-lpthread”选项。 功能创建一个新的线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *
(*start_routine)(void*), void *arg);
参数
thread:返回线程ID
attr:设置线程的属性attr为NULL表示使用默认属性
start_routine:是个函数地址线程启动后要执行的函数
arg:传给线程启动函数的参数
返回值成功返回0失败返回错误码并不设置error错误码因为错误码会针对整个进程 1.线程创建 pthread_create
class ThreadDate//定义一个类传给每个创建的新线程来命名区分它们
{
public:pthread_t tid;char namebuffer[64];int number;
};
void* thread_routine(void *args)//新线程调用的函数
{ThreadDate *td static_castThreadDate *(args);//安全的进行强制类型转化int cnt 10;while(cnt){cout new thread create success,name: td-namebuffer cnt:cnt--endl;sleep(1);}return nullptr;
}
int main()
{vectorThreadDate* threads;
#define NUM 10for(int i0;iNUM;i)//主线程创建十个新线程{ ThreadDate *td new ThreadDate();//char namebuffer[64];td-number i1;snprintf(td-namebuffer,sizeof(td-namebuffer),%s:%d,thread,i1);pthread_create(td-tid,nullptr,thread_routine,td);//td是自定义类型传参时会拷贝threads.push_back(td);//创建成功时将每一个新线程信息保留}return 0;
}
传给新线程的变量定义在堆栈上的区别 当我们要创建多个线程时我们需要传参数void *arg来对每个线程进行命名来区分它们。如果我们定义char namebuffer[64];因为新线程与主线程的异步性如果定义到了循环里每次循环结束这个局部变量就已经被销毁了但被创建出来的进程还保留着指向它的地址那么这个地址中存的东西可能会被别的变量覆盖如果是new一个自定义类的空间是因为在堆上所以生命周期长不会被覆盖。
2.线程终止 pthread_exit / return ——退出信号 为什么没有见到线程退出的时候对应的退出信号 线程出异常收到信号整个进程会退出thread_join:默认就认为函数会调用成功不考虑异常的问题异常是进程要考虑的问题。 如果一个线程出了异常会影响其它进程吗-所有线程退出(健壮性或鲁棒性过差)。 一个线程出现野指针问题向所有PID为该值的线程都写入11号信号段错误一个线程出错这个进程就发生异常OS会回收该进程的资源剩余的线程也不会活下去。 进程不会相互影响因为进程有独立的资源。用exit()退出线程则会终止该线程所在的整个进程。
——返回值 pthread_exit(nullptr); return nullptr; 线程的函数跑完了也就相当于该线程跑完了,用return也可以结束。 返回值的参数类型是void *retvaljoin传入void**类型指针即可拿到该返回值。此处也可以用自定义类的方式作为返回值返回线程终止时的各种状态。
class ThreadReturn
{
public:int exit_code;int exit_result;
};
void* thread_routine(void *args)//可重入状态--是可重入函数
{//... ...ThreadReturn *tr new ThreadReturn();//ThreadReturn tr;//在栈上定义牵扯到会被释放的问题return (void*)tr;tr-exit_code 1;tr-exit_result 106;return (void*)tr;//右值//pthread_exit((void*)tr);
}
int main()
{//... ...for(auto iter : threads){ThreadReturn *ret nullptr;int n pthread_join(iter-tid,(void**)ret);//void ** retp;*retp,从库中获取指定线程的输出结果assert(n0);coutjion :iter-namebuffersuccess,exit_code: ret-exit_code,exit_result: ret-exit_resultendl;delete iter;} coutmain thread quitendl;return 0;
}
3.线程等待回收 pthread_join
线程退出时它的PCB也需要释放如果不等待会造成类似僵尸进程的问题(暂时无法查看)造成内存泄漏。1.所以我们要获取新线程的退出信息2.回收新线程的内存资源避免内存泄漏。 vectorThreadDate* threads;
#define NUM 10
for(int i0;iNUM;i)//主线程创建十个新线程{ ThreadDate *td new ThreadDate();td-number i1;snprintf(td-namebuffer,sizeof(td-namebuffer),%s:%d,thread,i1);pthread_create(td-tid,nullptr,thread_routine,td);//td是自定义类型传参时会拷贝threads.push_back(td);//创建成功时将每一个新线程信息保留}
for(auto iter : threads)//主线程打印出它所创建的所有新线程{coutcreate thread:iter-namebufferiter-tid suceessendl;}
for(auto iter : threads)//主线程回收创建出来的所有新线程{int n pthread_join(iter-tid,nullptr);assert(n0);coutjion :iter-namebuffersuccessendl;delete iter;}
线程返回值 为什么return和pthread_exit(nullpter)参数都是nullptr void **retval用来获取线程函数结束时返回的退出结果
4.线程取消pthread_cancel
//线程是可以被cancel取消的注意线程要被取消前提是这个线程已经被运行起来了。for(int i0;ithreads.size()/2;i)//主线程打印出它所创建的所有新线程{pthread_cancel(threads[i]-tid);//PTHREAD_CANCELED -1coutpthread_cancel :threads[i]-namebuffersuccessendl;}for(auto iter : threads){void *ret nullptr;//我们在thread_routine返回一个return (void*)100;int n pthread_join(iter-tid,(void**)ret);//void ** retp;*retp,从库中获取指定线程的输出结果assert(n0);coutdecjion :iter-namebuffersuccess,exit_code: (long long)retendl;delete iter;} coutmain thread quitendl;
实验结果显示cancel取消的进程返回jion回收的结果返回的都是-1(宏PTHREAD_CANCELED)没被取消的进程被jion回收的值都是我们在thread_routine中设定的返回值return (void*)100;
5.补充初步重新认识线程库(语言版)
任何语言在Linux中要实现多线程必定要使用Pthread库如何看待c11中的多线程c11的多线程在Linux环境中本质是对pthread库的封装 推荐使用C如果在纯Linux环境下推荐使用原生线程库。在Windows环境中语言对库的封装解决了平台的差异性可以跨平台。在主流的框架中直接使用的原生线程库。
#includeiostream
#includethread
#includeunistd.h
void thread_run()
{while(true){std::cout我是新进程...std::endl;sleep(1);}
}
int main()
{std::thread t1(thread_run);while(true){std::cout我是主线程...std::endl;sleep(1);}t1.join();return 0;
}
6.线程分离
线程是可以等待的等待的时候使用jion—阻塞式如果不等待就会造成PCB内存泄漏类似于僵尸进程的问题。线程不存在非阻塞式等待那如果不等待呢 —默认情况下新创建的线程是joinable的(jionable表示该线程必须被jion)线程退出后需要对其进行pthread_join操作否则无法释放资源从而造成系统泄漏。 —如果不关心线程的返回值join是一种负担这个时候我们可以告诉系统当线程退出时自动释放线程资源。这种情况就被成为线程分离。
6.1pthread_self()接口获取自己的线程ID
std::string changeId(const pthread_t thread_id)//格式化一下传入的线程ID
{char tid[128];snprintf(tid,sizeof(tid),0x%x,thread_id);return tid;
}
void *start_routine(void *args)
{pthread_detach();//1.join时还没分离std::string threadname static_castconst char*(args);while(true){std::coutthreadnamerunning...changeId(pthread_self())std::endl;sleep(1);}
}
int main()
{pthread_t tid;pthread_create(tid,nullptr,start_routine,(void*)thread 1);pthread_detach(tid);//2.确保在join之前分离std::string main_id changeId(pthread_self());std::coutmain thread running...\n主线程IDmain_id新线程IDchangeId(tid)std::endl;pthread_join(tid,nullptr);//回收tid的线程不关心返回值传nullptr
} 6.2 pthread_detach分离一个线程(参数是线程ID)
主线程join的时候新线程还没执行分离...;所以得在主函数的join之前,新线程创建之后进行分离。
7.补充创建一个线程对应库和内核所做的工作 操作系统对系统内的多进程库要对线程做管理——先描述(线程的属性:tid/独立栈/...)再组织。在库中要创建线程的结构体在库中用户级线程用户关心的线程属性在库中内核提供线程执行流的调度Linux中用户级线程:内核轻量级进程1:1。 用户级线程ID究竟是什么 堆栈之间有一个共享区库是一个磁盘文件(库文件)进程要访问一个库必须将它加载到内存映射到地址空间当中通过地址空间和页表找到库对应的共享区在库中每一个线程当被创建时在内核中给它创建对应的线程ID在库当中也要给它创建一个描述线程的结构体(TCB:)。所有线程的TCB都放在一个数组中线程ID就是该线程在库中TCB的地址。 所以当一个线程终止时它的返回值就通过它在库中的地址也就是线程ID放入TCB中join时即可拿到。而每个线程的私有栈都在它们库中的TCB中多个轻量级进程每个所对应的栈主线程的栈是在地址空间中其它线程的栈在线程库中。 底层创建轻量级进程是库调用clone创建的。 主线程调用库创建新线程库先为新线程创建一个TCB然后调用Linux提供创建轻量级进程的接口clone将创建好的TCB对应的回调方法、私有栈、参数传递给clone所以线程一旦创建好了就会依赖我们的原生线程库。
线程的局部存储 想给线程定义一些私有的属性不想放在栈上或者malloc在堆上就想这个线程在运行起来后就天然具有独立的空间你就可以设置私有属性这种局部存储就是介于全局变量和局部变量之间的线程特有的一种属性。
__thread int thread_specific_variable;
8.自定义实现原生线程的封装
.hpp里面既包括方法的声明也包括方法的定义在使用时只要包括它即可。
#pragma once
#include iostream
#include string
#include pthread.h
#include cstring
#include functional
#include cassert
class thread;
class context//上下文当成一个大号的结构体——解决this指针的问题
{
public:Thread *this_;void *args_;
public:context():this_(nullptr),args_(nullptr);{}~context(){}
};class thread
{
public://using func_t std::functionvoid*(void*);typedef std::functionvoid*(void*) func_t;//C的函数对象const int num 1024;
public:Thread(func_t func,void *args,int number)//构造函数初始化func和用number线程name:func_(func),args_(args){char buffer[num];//c99支持//name_ thread_;//name_std::to_string(number);snprintf(buffer,sizeof buffer,thread-%d,number);name_ buffer;}//在类内创建线程想让线程执行对应的方法需要将方法设置成为staticstatic void *start_routine(void *args)//类内成员隐含this会和pthread_create参数不一致会有两个参数导致不一致{context *ctx static_castcontext *(args);void *ret ctx-this_-run(ctx-args_);delete ctx;return ret;//静态方法不能调用成员方法和成员变量只能调用静态成员方法和变量//return func_(args_);//复习类中的static修饰}void start(){context *ctx new context();ctx-this_ this;ctx-args_ args_;int n pthread_create(tid_,nullptr,start_routine,ctx);//函数是C规则调用c式接口assert(n 0);//编译debug的方式发布的时候存在release方式发布assert就不存在了n就只被定义没使用(void)n;//意料之外用if/异常意料之中用assert}void join(){int n pthread_join(tid_,nullptr);assert(n0);(void)n;}void *run(void *args){func_(args);}~Thread(){//do nothing}
private:std::string name_;func_t func_;//线程未来要运行时所要执行的函数pthread_t tid_;void *args_;//未来执行时带的参数
};