电商网站制作教程,正规品牌网站设计,山东东营网络科技有限公司,百度答主招募入口官网目录
线程等待
线程退出
线程的优缺点
线程独占和共享的数据 我们说Linux是用进程模拟的线程#xff0c;所以Linux中只有轻量级进程的概念#xff0c;但是#xff0c;用户是只认线程的#xff0c;所以我们有一个叫原生线程库的东西#xff0c;它就负责把轻量级进程的系…目录
线程等待
线程退出
线程的优缺点
线程独占和共享的数据 我们说Linux是用进程模拟的线程所以Linux中只有轻量级进程的概念但是用户是只认线程的所以我们有一个叫原生线程库的东西它就负责把轻量级进程的系统调用进行封装转成线程相关的接口提供给用户。 为什么叫原生呢就是说只要你装Linux系统就必须装这个库就是默认装好的库。 有关线程的接口在3号手册就证明它们不是系统调用因为系统调用在2号手册。当然也有创建轻量级进程的系统调用 man clone它是在二号手册 我们可以说其实pthread_create其实就是封装了这个系统调用 pthread_create创建新线程时第一个参数是输出型参数会带回来一个id值那么这个值和LWP之间是什么关系呢我们可以断定的认为它们是一对一的关系用户层使用id值内核只用LWP我们可以打印一下这个id值我们可以把id值改成十六进制 这个值很大其实它就是一个地址位于栈和堆之间的共享区的一个地址我们后面会有讲解 这是主进程获取新线程的id值那么一个线程如何获取它自己的id值呢 man pthread_self 这个函数就是谁调用它就获取谁的id值 我们从主线程和新线程中获取的新线程的id肯定是一样的 新线程和主线程谁先运行呢这个是不确定的是由调度器来决定的 pthread_create的第四个参数是给新线程要执行的函数传的参数是void*类型我们之前都传的是nullptr我们可以传一个名字 新主线程大部分资源是共享的比如地址空间是共享的我如果定义一个全局变量那么新主进程都可以看见 我们可以看到新线程对于全局数据的修改主线程也是可以看到的 线程等待 主线程如果退出的话不管新线程是否退出整个进程都会退出意味着资源会释放所有线程都会退出。所以我们往往要求主线程是最后退出的 并且线程也是要被等待的否则会像进程那样发生内存泄漏的问题线程等待就要用到下面这个接口 man pthread_join 这个接口也是默认是阻塞式等待的 第一个参数是要等待哪个线程 第二个参数是等待到了什么东西因为新线程的返回值是void*类型的所以要用void*类型去接收它是一个输出型参数所以我要传的参数是void*的指针所以是void** 我们简单来用一下 void *newthreadrun(void *args)
{char *str (char *)args;int cnt 5;while (cnt--){cout str is running endl;sleep(1);}return (void *)12345;
}
int main()
{pthread_t tid;pthread_create(tid, nullptr, newthreadrun, (void *)pthread-1);void *ret nullptr;int n pthread_join(tid, ret);if (n 0){cout return val is: (long long)ret endl;//指针64位下是8个字节强转成long long不会损失精度}return 0;
}如果说线程出异常比如除零那么整个进程都会退出这是因为信号是给进程发送的一个线程出了问题那么就是这个进程出了问题整个进程都要被杀死。 所以我们普遍认为多进程的代码往往健壮性不好当然这是普遍来说的如果一个多线程的代码写的很好健壮性也是不成问题的 所以pthread_join是不考虑新线程的异常情况的因为只要异常整个进程都退出了根本不存在等待的问题它只考虑代码运行完结果是否正确 线程退出 如果对一个线程使用exit会怎样呢它会使整个进程退出因为exit本身就是用来终止进程的而不是用来终止线程的 所以我们有一个pthread_exit用来终止线程 man pthread_exit 参数就是像return后面的数据一样就是想返回什么 上面这种方法是新线程主动退出那如果是主线程想让新线程退出该怎么办呢可以用下面的接口 man pthread_cancel 这里如果取消掉新线程并且等待新线程那么n0ret-1这里的-1 其实就是一个宏表示线程被取消了 所以进程退出有三种方式returnpthread_exit和pthread_cancel 线程的优缺点 优点 1.创建一个新线程的代价比创建一个新进程小很多因为创建一个线程只需要创建一个PCB而进程需要地址空间页表文件描述表等等 2.与进程之间的切换相比线程之间的切换需要操作系统做的工作要少很多 要解释这个我们就不得不提到CPU中的cache缓存了CPU要执行某行代码不是只把这行代码放到CPU中而是要把执行的代码及周围的代码都放到cache中这样CPU要执行的下一条代码就很有可能已经在缓存中了对于不同的线程切换来说这是有几率命中的但是对于不同的进程切换肯定要换掉缓存中的代码 我们可以用lscpu看一下云服务器的CPU配置 3.IO操作基本大部分时间都是在等所以我们可以趁着等的时间多创建几个线程执行其他的任务 缺点 1.一般计算密集型的进程的线程数量和CPU的数量相同如果线程数量太多也会增加调度的开销 2.健壮性降低线程之间缺乏保护就容易导致产生不良影响 3.编程难度提高编写与调试一个多线程程序比单线程程序困难得多 线程独占和共享的数据 线程独占 1.线程的硬件上下文CPU寄存器内部的值从调度角度而言 2.线程的独立栈结构从常规运行的角度 3.线程的ID 4.信号屏蔽字 5.调度优先级 6.errno变量 线程共享 1.函数和全局变量 2.文件描述符表 3.各种信号的处理方式 4.当前工作目录 5.用户id和组id这些id标识了线程的拥有者和所属组 我们如果想创建多个线程可以这样 void *newthreadrun(void *args)
{char *str (char *)args;int cnt5;while(cnt--){cout str is running endl;sleep(1);}delete[] str;//new[]的空间要delete[]return nullptr;
}
int main()
{pthread_t tid;vectorpthread_tf;for (int i 1; i 5; i){char*buffernew char[64];//这里必须new否则五个线程看到的是一块空间snprintf(buffer, 64, pthread-%d, i);//不能用sizeof因为sizeof(buffer)8pthread_create(tid, nullptr, newthreadrun, buffer);f.push_back(tid);}for(auto e:f){pthread_join(e,nullptr);}return 0;
} 我们也可以将线程和C中的类和对象联系应用起来 templateclass T
T add(T x, T y)
{return x y;
}
template class T
class threaddata
{
public:threaddata(const char *str, functionT(T, T) task): _name(str), _task(task){delete[] str;}T Dotask(T x, T y){return _task(x, y);}string _name;functionT(T, T) _task;T _result;
};void *newthreadrun(void *args)
{threaddataint *p (threaddataint *)args;p-_result p-Dotask(10, 20);cout p-_name get result: p-_result endl;delete p;return nullptr;
}int main()
{pthread_t tid;vectorpthread_t f;for (int i 1; i 5; i){char *buffer new char[64];snprintf(buffer, 64, pthread-%d, i);threaddataint *ptd new threaddataint(buffer, addint);pthread_create(tid, nullptr, newthreadrun, ptd);f.push_back(tid);}for (auto e : f){pthread_join(e, nullptr);}return 0;
}