网站为何要屏蔽百度蜘蛛,网页设计制作教程:一个页面的完全制作,杭州网站建设洛洛科技,公司宣传推广方案文章目录 线程的概述理解线程Linux中的线程重新理解的进程Windows的线程线程的优点线程的缺点理解线程调度成本低 进程VS线程 线程控制创建线程等待线程线程函数传参线程的返回值新线程的返回值新线程返回值错误返回值为类对象 创建多线程线程的终止线程的分离pthread_detach 线… 文章目录 线程的概述理解线程Linux中的线程重新理解的进程Windows的线程线程的优点线程的缺点理解线程调度成本低 进程VS线程 线程控制创建线程等待线程线程函数传参线程的返回值新线程的返回值新线程返回值错误返回值为类对象 创建多线程线程的终止线程的分离pthread_detach 线程的概述
理解线程
线程线程是在进程内部PCB运行是CPU内部调度的基本单位。 Linux中的线程
在Linux中线程执行的是进程代码的一部分也就是说线程是进程的实体可以看作是进程内的一个执行单元我们将这些不同的执行单元称之为轻量级进程不同线程之间可以通过共享内存来进行通信。
这里可以举一个例子要想有一个幸福的家庭家庭成员就要执行好自己的事情这样才能成就一个幸福的家庭。
PCB大部分属性都包含了线程的属性复用PCB用PCB统一表示执行流这样线程就不需要给线程单独设计数据结构和调度算法。这样维护的数据结构和调度算法的代码就少。
在CPU看来不需要区分task_struct是进程还是线程都叫做执行流。Linux执行流都是轻量级进程。Linux使用进程模拟线程。 重新理解的进程
以前我们学习的进程内核数据结构进程的数据代码这是我们之前理解的。以前我们强调的是代码的执行流内部只有一个执行流而现在我们的内部有多个执行流。
因此以内核观点给进程重新下一个定义承担分配系统资源的基本实体。 Windows的线程
操作系统设计一个线程需要新建、创建、销毁、管理等线程要不要和进程产生关系呢 操作系统需要对线程进行管理先描述struct tcb再组织。
struct tcb
{//线程的id,优先级状态上下文连接属性...
}Windows提供了真实的线程控制块不是复用PCB属性这样维护的数据结构和调度算法的代码就高。 线程的优点
创建一个新线程的代价要比创建一个新进程小得多与进程之间的切换相比线程之间的切换需要操作系统做的工作要少很多线程占用的资源要比进程少很多能充分利用多处理器的可并行数量在等待慢速I/O操作结束的同时程序可执行其他的计算任务计算密集型应用为了能在多处理器系统上运行将计算分解到多个线程中实现I/O密集型应用为了提高性能将I/O操作重叠。线程可以同时等待不同的I/O操作 线程的缺点 性能损失 一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型 线程的数量比可用的处理器多那么可能会有较大的性能损失这里的性能损失指的是增加了额外的 同步和调度开销而可用的资源不变。 健壮性降低 编写多线程需要更全面更深入的考虑在一个多线程程序里因时间分配上的细微偏差或者因共享了 不该共享的变量而造成不良影响的可能性是很大的换句话说线程之间是缺乏保护的。 缺乏访问控制 进程是访问控制的基本粒度在一个线程中调用某些OS函数会对整个进程造成影响。 编程难度提高 编写与调试一个多线程程序比单线程程序困难得多 理解线程调度成本低
线程在同一个进程内部共享相同的地址空间和大部分资源因此在创建、销毁或者切换线程时无需像进程那样复制和维护额外的资源。这减少了资源管理的开销。
硬件角度线程在同一核心上连续执行时由于其数据可能保持在该核心的缓存中可以更高效地利用缓存从而提高数据访问的速度。这可以减少因缓存未命中而引起的额外延迟进而降低线程切换的成本。 进程VS线程
进程是资源分配的基本单位线程是调度的基本单位线程共享进程数据但也拥有自己的一部分数据 线程ID 一组寄存器硬件上下文数据体现出线程可以动态运行 栈线程在运行的时候本质是运行函数会形成临时变量临时变量会被保存在每个线程的栈区 errno 信号屏蔽字 调度优先级
进程的多个线程共享 同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程 中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境: 文件描述符表、每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)、当前工作目录、用户id和组id。
进程和线程的关系 线程控制
在Linux系统中没有线程只有轻量级进程这个轻量级进程实际上就是线程因为没有单独设计TCB。因此Linux操作系统不会直接给我们提供线程的系统调用只会提供创建轻量级进程的系统调用接口。Linux系统存在一个中间软件层有一个pthred库是自带的原生线程库对该轻量级进程接口进行封装按照线程的接口提供给用户。所以说Linux是用户级线程Windows是内核级线程。
创建线程
创建一个进程的函数接口
#include pthread.hint pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);Compile and link with -pthread. 参数 thread:返回线程ID是一个输出型参数 attr:设置线程的属性attr为NULL表示使用默认属性 start_routine:是个函数地址线程启动后要执行的函数 arg:传给线程启动函数的参数
返回值成功返回0失败返回错误码内容未定义。
等待线程
等待线程的函数接口
#include pthread.hint pthread_join(pthread_t thread, void**retval);Compile and link with -pthread.
参数 thread:线程ID value_ptr:它指向一个指针后者指向线程的返回值 返回值成功返回0失败返回错误码
代码示例
#includeiostream
#includestring
#includepthread.h
#includeunistd.hvoid *threadRun(void *args)
{int cnt10;while(cnt){std::coutnew thread run ...,cnt:cnt--std::endl;sleep(1);}return nullptr;
}int main()
{pthread_t tid;int npthread_create(tid,nullptr,threadRun,(void*)thread 1);if(n!0){std::cerrcreate thread errorstd::endl;return 1;}std::coutmain thread join begin...std::endl;npthread_join(tid,nullptr); //保证main线程最后退出if(n0){std::coutmain thread wait sucessstd::endl;}return 0;
}上述代码中子线程完成之后主线程才退出。
这里的tid是什么 tid是线程库中的一个地址是虚拟地址。
线程函数传参
主线程数据可传递给新线程 代码演示
#includeiostream
#includestring
#includepthread.h
#includeunistd.hvoid *threadRun(void *args)
{//std::string name(const char*)args;int a*(int*)args;int cnt10;while(cnt){std::couta run ...,cnt:cnt--std::endl;sleep(1);}return nullptr;
}std::string PrintToHex(pthread_t tid)
{char buffer[64];snprintf(buffer,sizeof(buffer),0x%lx,tid);return buffer;
}int main()
{pthread_t tid;int a100;int npthread_create(tid,nullptr,threadRun,(void*)a);if(n!0){std::cerrcreate thread errorstd::endl;return 1;}std::string tid_strPrintToHex(tid);std::couttid:tid_strstd::endl;std::coutmain thread join begin...std::endl;npthread_join(tid,nullptr); //保证main线程最后退出if(n0){std::coutmain thread wait sucessstd::endl;}return 0;
}这里传递参数可以是任意类型可以传递类对象在传递类对象时传递的是地址。也就是说我们可以传递多个参数。
代码示例
#includeiostream
#includestring
#includepthread.h
#includeunistd.hclass ThreadData
{
public:std::string name;int num;
};void *threadRun(void *args)
{//std::string name(const char*)args;//int a*(int*)args;ThreadData *tdstatic_castThreadData*(args);//(ThreadData*)args int cnt10;while(cnt){std::couttd-name run ...,num is:td-num,cnt:cnt--std::endl;sleep(1);}return nullptr;
}std::string PrintToHex(pthread_t tid)
{char buffer[64];snprintf(buffer,sizeof(buffer),0x%lx,tid);return buffer;
}int main()
{pthread_t tid;ThreadData td;td.namethread-1;td.num1;int npthread_create(tid,nullptr,threadRun,(void*)td);if(n!0){std::cerrcreate thread errorstd::endl;return 1;}std::string tid_strPrintToHex(tid);std::couttid:tid_strstd::endl;std::coutmain thread join begin...std::endl;npthread_join(tid,nullptr); //保证main线程最后退出if(n0){std::coutmain thread wait sucessstd::endl;}return 0;
}上述做法不推荐td在主线程中破坏了主线程的完整性和独立性。td 是一个局部变量其生命周期仅限于 main 函数。一旦 main 函数结束td 将会被销毁此时新线程仍然可能在尝试访问已经无效的内存从而导致未定义行为。另外如果此时还有一个线程使用的也是td访问的也是主函数中td变量那这两个线程中如果其中一个线程对td进行修改那么就会影响另一个线程。
换一种做法
在主线程中申请一个堆上的空间把堆上的地址拷贝给线程此时对空间就交给了新线程。如果还有第二个线程那就重新new一个堆上的空间交给新的线程。这样每一个线程都有属于自己的堆空间。另外在线程内部处理完后需要delete。
#includeiostream
#includestring
#includepthread.h
#includeunistd.hclass ThreadData
{
public:std::string name;int num;
};void *threadRun(void *args)
{//std::string name(const char*)args;//int a*(int*)args;ThreadData *tdstatic_castThreadData*(args);//(ThreadData*)args int cnt10;while(cnt){std::couttd-name run ...,num is:td-num,cnt:cnt--std::endl;sleep(1);}delete td;return nullptr;
}std::string PrintToHex(pthread_t tid)
{char buffer[64];snprintf(buffer,sizeof(buffer),0x%lx,tid);return buffer;
}int main()
{pthread_t tid;ThreadData *tdnew ThreadData(); //在堆上申请空间td-namethread-1;td-num1;int npthread_create(tid,nullptr,threadRun,td);if(n!0){std::cerrcreate thread errorstd::endl;return 1;}std::string tid_strPrintToHex(tid);std::couttid:tid_strstd::endl;std::coutmain thread join begin...std::endl;npthread_join(tid,nullptr); //保证main线程最后退出if(n0){std::coutmain thread wait sucessstd::endl;}return 0;
}线程的返回值
新线程的返回值
线程的返回值是void*那么主线程的函数就必须使用void **的参数返回int pthread_join(pthread_t thread, void**retval)。void**retval是一个输出型参数需要一个void*类型的变量即void* ret。void* ret是一个指针类型的变量有对应的空间在Linux中占8个字节在传递的时候是传递ret 是将地址传过去。
主线程拿到新线程的退出信息 代码
#includeiostream
#includestring
#includepthread.h
#includeunistd.hclass ThreadData
{
public:std::string name;int num;
};void *threadRun(void *args)
{//std::string name(const char*)args;//int a*(int*)args;ThreadData *tdstatic_castThreadData*(args);//(ThreadData*)args int cnt10;while(cnt){std::couttd-name run ...,num is:td-num,cnt:cnt--std::endl;sleep(1);}delete td;return (void*)111;
}std::string PrintToHex(pthread_t tid)
{char buffer[64];snprintf(buffer,sizeof(buffer),0x%lx,tid);return buffer;
}int main()
{pthread_t tid;ThreadData *tdnew ThreadData(); //在堆上申请空间td-namethread-1;td-num1;int npthread_create(tid,nullptr,threadRun,td);if(n!0){std::cerrcreate thread errorstd::endl;return 1;}std::string tid_strPrintToHex(tid);std::couttid:tid_strstd::endl;std::coutmain thread join begin...std::endl;void* codenullptr;//开辟了空间npthread_join(tid,code); //保证main线程最后退出if(n0){std::coutmain thread wait sucess,new thread exit code:(uint64_t)codestd::endl;}return 0;
} 可以根据退出信息就可以判断新线程是否完成对应的任务。
新线程返回值错误 上述代码故意让新线程出现野指针是的新线程出现错误。 上述代码时主线程新线程出错后让主线程等100s后再退出。
代码演示结果 主线程没有等100s后退出而是在新的进程异常后直接退出。
线程的返回值只有正确时的返回值一旦出现异常线程就会崩溃线程出现异常就会发信号给进程进程就会被杀掉即使进程里面有多个线程里面有一个线程出现错误整个进程都会被杀掉。 因此线程的在退出的时候只需要考虑正确的返回不考虑异常一旦异常整个进程都会崩溃包括主线程。
返回值为类对象
主线程创建并启动了一个新的线程通过 pthread_create 和 pthread_join 实现了线程的创建和等待。 在新线程中通过 ThreadData 类传递了操作数并在 threadRun 函数中计算了加法操作的结果。 最后主线程将计算结果打印出来展示了线程之间数据的传递和简单的同步操作。
#includeiostream
#includestring
#includepthread.h
#includeunistd.hclass ThreadData
{
public:int Excute(){return xy;}
public:std::string name;int x;int y;
};class ThreadResult
{
public:std::string print(){return std::to_string(x)std::to_string(y)std::to_string(result);}
public:int x;int y;int result;
};void *threadRun(void *args)
{//std::string name(const char*)args;//int a*(int*)args;ThreadData *tdstatic_castThreadData*(args);//(ThreadData*)args ThreadResult *resultnew ThreadResult();int cnt10;while(cnt){sleep(1);std::couttd-name run ...,cnt:cnt--std::endl;result-resulttd-Excute();result-xtd-x;result-ytd-y;break;}delete td;return (void*)result;
}std::string PrintToHex(pthread_t tid)
{char buffer[64];snprintf(buffer,sizeof(buffer),0x%lx,tid);return buffer;
}int main()
{pthread_t tid;ThreadData *tdnew ThreadData(); //在堆上申请空间td-namethread-1;td-x10;td-y20;int npthread_create(tid,nullptr,threadRun,td);if(n!0){std::cerrcreate thread errorstd::endl;return 1;}std::string tid_strPrintToHex(tid);std::couttid:tid_strstd::endl;std::coutmain thread join begin...std::endl;ThreadResult* resultnullptr;//开辟了空间npthread_join(tid,(void**)result); //保证main线程最后退出if(n0){std::coutmain thread wait sucess,new thread exit code:result-print()std::endl;}return 0;
} 创建多线程
#includeiostream
#includestring
#includevector
#includepthread.h
#includeunistd.husing namespace std;const int num10;void *threadrun(void* args)
{string namestatic_castconst char*(args);while(true){coutnameis runningendl;sleep(1);break;}return args;
}string PrintToHex(pthread_t tid)
{char buffer[64];snprintf(buffer,sizeof(buffer),0x%lx,tid);return buffer;
}int main()
{vectorpthread_t tids;for(int i0;inum;i){pthread_t tid;char *namenew char[128];snprintf(name,128,thread-%d,i1);//名字pthread_create(tid,nullptr,threadrun,name); //传递线程的名字//保存所有线程的id信息tids.push_back(tid);}//等待for(auto tid:tids){void *namenullptr;pthread_join(tid,name);//coutPrintToHex(tid) quit...endl;cout(const char*)name quit...endl;delete (const char*)name; }sleep(1); return 0;
}在主函数中首先创建一个 vector 容器 tids用于存储所有线程的 ID。使用for循环创建num个线程。在第一个for循环中配一个新的字符数组name来存储线程名字使用 snprintf 将线程名字格式化为 thread-i 的形式调用 pthread_create 函数创建线程传递线程名字作为参数将每个线程的 ID 存入 tids 向量。第二个for循环中等待所有进程结束使用 pthread_join 函数等待线程结束获取线程返回的 name并输出线程名字加上 “quit…”删除线程名字的内存以防止内存泄漏。
运行结果
线程的终止
新的线程如何终止函数return。
主线程如何终止 主线程对应的main函数结束那么主线程结束表示整个进程结束。此时如果还有新线程还没有结束那么新线程就会被提前终止因此有线程等待是的主线程最后结束。
使用线程终止函数
#include pthread.hvoid pthread_exit(void *retval);Compile and link with -pthread.
代码测试
#includeiostream
#includestring
#includevector
#includepthread.h
#includeunistd.h
#includestdlib.husing namespace std;const int num10;void *threadrun(void* args)
{string namestatic_castconst char*(args);while(true){coutnameis runningendl;sleep(3);break;}//exit(1);//exit是进程退出不能终止线程//return args;pthread_exit(args);
}string PrintToHex(pthread_t tid)
{char buffer[64];snprintf(buffer,sizeof(buffer),0x%lx,tid);return buffer;
}int main()
{vectorpthread_t tids;for(int i0;inum;i){pthread_t tid;char *namenew char[128];snprintf(name,128,thread-%d,i1);//名字pthread_create(tid,nullptr,threadrun,name); //传递线程的名字//保存所有线程的id信息//tids.push_back(tid);tids.emplace_back(tid);}//等待for(auto tid:tids){void *namenullptr;pthread_join(tid,name);//coutPrintToHex(tid) quit...endl;cout(const char*)name quit...endl;delete (const char*)name; }sleep(1); return 0;
}还有一个终止线程函数
#include pthread.hint pthread_cancel(pthread_t thread);Compile and link with -pthread.
这个实际上是取消一个线程发送一个取消请求给线程一般都是使用主线程取消其他线程。
代码示例
#includeiostream
#includestring
#includevector
#includepthread.h
#includeunistd.h
#includestdlib.husing namespace std;const int num10;void *threadrun(void* args)
{string namestatic_castconst char*(args);while(true){coutnameis runningendl;sleep(1);//break;}//exit(1);//exit是进程退出不能终止线程//return args;pthread_exit(args);
}string PrintToHex(pthread_t tid)
{char buffer[64];snprintf(buffer,sizeof(buffer),0x%lx,tid);return buffer;
}int main()
{vectorpthread_t tids;for(int i0;inum;i){pthread_t tid;char *namenew char[128];snprintf(name,128,thread-%d,i1);//名字pthread_create(tid,nullptr,threadrun,name); //传递线程的名字//保存所有线程的id信息//tids.push_back(tid);tids.emplace_back(tid);}sleep(5);//等待for(auto tid:tids){pthread_cancel(tid);//取消coutcancel:PrintToHex(tid)endl;void *resultnullptr; //现成被取消被取消的线程的返回结果是-1pthread_join(tid,result);//coutPrintToHex(tid) quit...endl;cout(long long int)result quit...endl;//delete (const char*)name; }sleep(1); return 0;
}线程的分离pthread_detach
线程的分离函数接口
#include pthread.hint pthread_detach(pthread_t thread);Compile and link with -pthread.
如果一个线程被创建默认是需要joinable的必须被join如果一个线程被分离线程的工作状态是分离状态不能被join。
进程分离一个线程依旧属于线程但是不需要被主线程等待。
#includeiostream
#includestring
#includevector
#includepthread.h
#includeunistd.h
#includestdlib.husing namespace std;const int num10;void *threadrun(void* args)
{pthread_detach(pthread_self());string namestatic_castconst char*(args);while(true){coutnameis runningendl;sleep(3);break;}//exit(1);//exit是进程退出不能终止线程//return args;pthread_exit(args);
}string PrintToHex(pthread_t tid)
{char buffer[64];snprintf(buffer,sizeof(buffer),0x%lx,tid);return buffer;
}int main()
{vectorpthread_t tids;for(int i0;inum;i){pthread_t tid;char *namenew char[128];snprintf(name,128,thread-%d,i1);//名字pthread_create(tid,nullptr,threadrun,name); //传递线程的名字//保存所有线程的id信息//tids.push_back(tid);tids.emplace_back(tid);}//等待for(auto tid:tids){void *resultnullptr; //现成被取消被取消的线程的返回结果是-1int npthread_join(tid,result);//coutPrintToHex(tid) quit...endl;cout(long long int)result quit...,n:nendl;//delete (const char*)name; }sleep(100); return 0;
}上述代码表示主线程创建一个新线程但是新的线程被分离了没有被等待因此n返回为22。.
如果被分离的线程出异常依然会影响进程会导致进程直接崩溃。