一、线程优缺1.线程的优点创建一个新线程的代价要比创建一个新进程小得多与进程之间的切换相比线程之间的切换需要操作系统做的工作要少很多最主要的区别是线程的切换虚拟内存空间依然是相同的但是进程切换是不同的。这两种上下文切换的处理都是通过操作系统内核来完成的。内核的这种切换过程伴随的最显著的性能损耗是将寄存器中的内容切换出。另外一个隐藏的损耗是上下文的切换会扰乱处理器的缓存机制。简单的说一旦去切换上下文处理器中所有已经缓存的内存地址一瞬间都作废了。还有一个显著的区别是当你改变虚拟内存空间的时候处理的页表缓冲 TLB快表会被全部刷新这将导致内存的访问在一段时间内相当的低效。但是在线程的切换中不会出现这个问题当然还有硬件 cache。线程占用的资源要比进程少能充分利用多处理器的可并行数量在等待慢速 I/O 操作结束的同时程序可执行其他的计算任务计算密集型应用为了能在多处理器系统上运行将计算分解到多个线程中实现I/O 密集型应用为了提高性能将 I/O 操作重叠。线程可以同时等待不同的 I/O 操作。2.线程的缺点性能损失一个很少被外部事件阻塞的计算密集型线程往往无法与其它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多那么可能会有较大的性能损失这里的性能损失指的是增加了额外的同步和调度开销而可用的资源不变。健壮性降低编写多线程需要更全面更深入的考虑在一个多线程程序里因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的换句话说线程之间是缺乏保护的。缺乏访问控制进程是访问控制的基本粒度在一个线程中调用某些 OS 函数会对整个进程造成影响。编程难度提高编写与调试一个多线程程序比单线程程序困难得多3. 线程异常单个线程如果出现除零野指针问题导致线程崩溃进程也会随着崩溃线程是进程的执行分支线程出异常就类似进程出异常进而触发信号机制终止进程进程终止该进程内的所有线程也就随即退出4. 线程用途合理的使用多线程能提高 CPU 密集型程序的执行效率合理的使用多线程能提高 IO 密集型程序的用户体验如生活中我们一边写代码一边下载开发工具就是多线程运行的一种表现二、Linux 进程 VS 线程 --- 哪些资源共享哪些独占进程间具有独立性线程共享地址空间也就共享进程资源2.1 进程和线程进程是资源分配的基本单位线程是调度的基本单位线程共享进程数据但也拥有自己的一部分 私有 数据:线程 ID一组寄存器线程的上下文数据栈errno信号屏蔽字调度优先级2.2 进程的多个线程共享同一地址空间因此 Text Segment、Data Segment 都是共享的如果定义一个函数在各线程中都可以调用如果定义一个全局变量在各线程中都可以访问到除此之外各线程还共享以下进程资源和环境:文件描述符表每种信号的处理方式 (SIG_IGN、SIG_DFL 或者自定义的信号处理函数)当前工作目录用户 id 和组 id进程和线程的关系如下图:3、Linux 线程控制Linux的线程控制用的是pthread库原生库所以在编译链接时需要带上“-lpthread”3.1 POSIX 线程库与线程有关的函数构成了一个完整的系列绝大多数函数的名字都是以 “pthread_” 打头的要使用这些函数库要通过引入头文pthread.h链接这些线程函数库时要使用编译器命令的 “-lpthread” 选项3.2 创建线程pthread_create 函数NAME pthread_create - create a new thread LIBRARY POSIX threads library (libpthread, -lpthread) SYNOPSIS #include pthread.h int pthread_create(pthread_t *restrict thread, const pthread_attr_t *restrict attr, void *(*start_routine)(void *), void *restrict arg); RETURN VALUE On success, pthread_create() returns 0; on error, it returns an error number, and the con‐ tents of *thread are undefined.参数thread返回线程 IDattr设置线程的属性attr为NULL表示使用默认属性start_routine是个函数地址线程启动后要执行的函数arg传给线程启动函数的参数返回值成功返回 0失败返回错误码3.3 返回自己的tidpthread_self 函数NAME pthread_self - obtain ID of the calling thread LIBRARY POSIX threads library (libpthread, -lpthread) SYNOPSIS #include pthread.h pthread_t pthread_self(void); RETURN VALUE This function always succeeds, returning the calling threads ID.代码样例#include iostream #include cstdio #include string #include unistd.h #include Task.hpp void* Rounted(void* args) { std::string name static_castconst char*(args); while(true) { printf(我是新线程%s, tid:0x%lx, pid: %d\n, name.c_str(), pthread_self(), getpid()); sleep(1); } } int main() { const int num 10; for(int i 1; i num; i) { pthread_t tid; char threadname[64]; snprintf(threadname, sizeof threadname, thread-%d, i); int n pthread_create(tid, nullptr, Rounted, threadname); (void)n; sleep(1); } while(true) { printf(我是主线程:tid:0x%lx, pid: %d\n, pthread_self(), getpid()); sleep(1); } // pthread_t tid; // int n pthread_create(tid, nullptr, Rounted,(void*)thread-1); // printf(new thread id:0x%lx\n, tid); // while(true) // { // printf(我是主线程:tid:0x%lx, pid: %d\n, pthread_self(), getpid()); // sleep(1); // } return 0; }运行代码会发现tid是一个很大是数这个数是什么呢其实是线程在虚拟地址的起始地址打印出来的tid是通过pthread库中有函数pthread_self得到的它返回一个pthread_t类型的变量指代的是调用pthread_self函数的线程的 “ID”。怎么理解这个 “ID” 呢这个 “ID” 是pthread库给每个线程定义的进程内唯一标识是pthread库维持的。由于每个进程有自己独立的内存空间故此 “ID” 的作用域是进程级而非系统级内核不认识。其实pthread库也是通过内核提供的系统调用例如clone来创建线程的而内核会为每个线程创建系统全局唯一的 “ID” 来唯一标识这个线程。使用PS命令查看线程信息zhangsanhcss-ecs-f571:~$ ps -aL PID LWP TTY TIME CMD 1747585 1747585 pts/2 00:00:00 ps-L选项打印线程信息LWP是什么呢LWP得到的是真正的线程 ID。之前使用pthread_self得到的数实际上是一个地址在虚拟地址空间上的一个地址通过这个地址可以找到关于这个线程的基本信息包括线程栈线程栈寄存器等属性。在ps -aL得到的线程 ID有一个线程 ID 和进程 ID 相同这个线程就是主线程主线程的栈在虚拟地址空间的栈上而其他线程的栈是在共享区堆栈之间因为pthread系列函数都是pthread库提供给我们的。而pthread库是在共享区的。所以除了主线程之外的其他线程的栈都在共享区。3.4 线程终止如果需要只终止某个线程而不终止整个进程可以有三种方法:从线程函数return。这种方法对主线程不适用从main函数return相当于调用exit。线程可以调用pthread_exit终止自己。一个线程可以调用pthread_cancel终止同一进程中的另一个线程。pthread_exit 函数NAME pthread_exit - terminate calling thread LIBRARY POSIX threads library (libpthread, -lpthread) SYNOPSIS #include pthread.h [[noreturn]] void pthread_exit(void *retval); RETURN VALUE This function does not return to the caller.参数retval线程结束后返回的值相当于return retval中的retvalpthread_cancel 函数NAME pthread_cancel - send a cancelation request to a thread LIBRARY POSIX threads library (libpthread, -lpthread) SYNOPSIS #include pthread.h int pthread_cancel(pthread_t thread); RETURN VALUE On success, pthread_cancel() returns 0; on error, it returns a nonzero error number.参数thread线程 ID返回值成功返回 0失败返回错误码3.5 线程等待为什么需要线程等待已经退出的线程其空间没有被释放仍然在进程的地址空间内。创建新的线程不会复用刚才退出线程的地址空间。功能等待线程结束原型int pthread_join(pthread_t thread, void **value_ptr);参数thread线程 IDvalue_ptr它指向一个指针后者指向线程的返回值返回值成功返回 0失败返回错误码调用该函数的线程将挂起等待直到id为thread的线程终止。thread线程以不同的方法终止通过pthread_join得到的终止状态是不同的总结如下:如果thread线程通过return返回value_ptr所指向的单元里存放的是thread线程函数的返回值。如果thread线程被别的线程调用pthread_cancel异常终掉value_ptr所指向的单元里存放的是常数PTHREAD_CANCELED。如果thread线程是自己调用pthread_exit终止的value_ptr所指向的单元存放的是传给pthread_exit的参数。如果对thread线程的终止状态不感兴趣可以传NULL给value_ptr参数。3.6 分离线程默认情况下新创建的线程是joinable的线程退出后需要对其进行pthread_join操作否则无法释放资源从而造成系统泄漏。如果不关心线程的返回值join是一种负担这个时候我们可以告诉系统当线程退出时自动释放线程资源。int pthread_detach(pthread_t thread);可以是线程组内其他线程对目标线程进行分离也可以是线程自己分离:pthread_detach(pthread_self());三、线程 ID 及进程地址空间布局pthread_create函数会产生一个线程 ID存放在第一个参数指向的地址中。该线程 ID 和前面说的线程 ID 不是一回事。前面讲的线程 ID 属于进程调度的范畴。因为线程是轻量级进程是操作系统调度器的最小单位所以需要一个数值来唯一表示该线程。pthread_create函数第一个参数指向一个虚拟内存单元该内存单元的地址即为新创建线程的线程 ID属于 NPTL 线程库的范畴。线程库的后续操作就是根据该线程 ID 来操作线程的。线程库 NPTL 提供了pthread_self函数可以获得线程自身的 IDpthread_t pthread_self(void);pthread_t到底是什么类型呢取决于实现。对于 Linux 目前实现的 NPTL 实现而言pthread_t类型的线程 ID本质就是一个进程地址空间上的一个地址。四、线程封装#pragma once #include iostream #include string #include functional #include pthread.h namespace myThread { static int gnumber 1; using callback_t std::functionvoid(); enum class TSTATUS { THREAD_NEW, THREAD_RUNNING, THREAD_STOP }; std::string Status2String(TSTATUS s) { switch (s) { case TSTATUS::THREAD_NEW: return THREAD_NEW; case TSTATUS::THREAD_RUNNING: return THREAD_RUNNING; case TSTATUS::THREAD_STOP: return THREAD_STOP; default: return UNKNOWN; } } std::string IsJoined(bool joinable) { return joinable ? true : false; } class Thread { private: void ToRunning() { _status TSTATUS::THREAD_RUNNING; } void ToStop() { _status TSTATUS::THREAD_STOP; } static void *ThreadRoutine(void *args) { Thread *self static_castThread *(args); pthread_setname_np(self-_tid, self-_name.c_str()); self-_cb(); self-ToStop(); return nullptr; } public: Thread(callback_t cb) : _tid(-1), _status(TSTATUS::THREAD_NEW), _joinable(true), _cb(cb), _result(nullptr) { _name New-Thread- std::to_string(gnumber); } bool Start() { int n pthread_create(_tid, nullptr, ThreadRoutine, this); if (n ! 0) return false; ToRunning(); return true; } void Join() { if (_joinable) { int n pthread_join(_tid, _result); if (n ! 0) { std::cerr join error: n std::endl; return; } (void)_result; _status TSTATUS::THREAD_STOP; } else { std::cerr error, thread join status: IsJoined(_joinable) std::endl; } } // 暂停 // void Stop() // restart() // { // // 让线程暂停 // } void Die() { if (_status TSTATUS::THREAD_RUNNING) { pthread_cancel(_tid); _status TSTATUS::THREAD_STOP; } } void Detach() { if (_status TSTATUS::THREAD_RUNNING _joinable) { pthread_detach(_tid); _joinable false; } else { std::cerr detach _name failed std::endl; } } void PrintInfo() { std::cout thread name : _name std::endl; std::cout thread _tid : _tid std::endl; std::cout thread _status : Status2String(_status) std::endl; std::cout thread _joinable : IsJoined(_joinable) std::endl; } ~Thread() { } private: std::string _name; pthread_t _tid; TSTATUS _status; bool _joinable; // 线程要有自己的任务处理即回调函数 callback_t _cb; // 线程退出信息 void *_result; }; }
Linux:线程优缺,线程控制
一、线程优缺1.线程的优点创建一个新线程的代价要比创建一个新进程小得多与进程之间的切换相比线程之间的切换需要操作系统做的工作要少很多最主要的区别是线程的切换虚拟内存空间依然是相同的但是进程切换是不同的。这两种上下文切换的处理都是通过操作系统内核来完成的。内核的这种切换过程伴随的最显著的性能损耗是将寄存器中的内容切换出。另外一个隐藏的损耗是上下文的切换会扰乱处理器的缓存机制。简单的说一旦去切换上下文处理器中所有已经缓存的内存地址一瞬间都作废了。还有一个显著的区别是当你改变虚拟内存空间的时候处理的页表缓冲 TLB快表会被全部刷新这将导致内存的访问在一段时间内相当的低效。但是在线程的切换中不会出现这个问题当然还有硬件 cache。线程占用的资源要比进程少能充分利用多处理器的可并行数量在等待慢速 I/O 操作结束的同时程序可执行其他的计算任务计算密集型应用为了能在多处理器系统上运行将计算分解到多个线程中实现I/O 密集型应用为了提高性能将 I/O 操作重叠。线程可以同时等待不同的 I/O 操作。2.线程的缺点性能损失一个很少被外部事件阻塞的计算密集型线程往往无法与其它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多那么可能会有较大的性能损失这里的性能损失指的是增加了额外的同步和调度开销而可用的资源不变。健壮性降低编写多线程需要更全面更深入的考虑在一个多线程程序里因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的换句话说线程之间是缺乏保护的。缺乏访问控制进程是访问控制的基本粒度在一个线程中调用某些 OS 函数会对整个进程造成影响。编程难度提高编写与调试一个多线程程序比单线程程序困难得多3. 线程异常单个线程如果出现除零野指针问题导致线程崩溃进程也会随着崩溃线程是进程的执行分支线程出异常就类似进程出异常进而触发信号机制终止进程进程终止该进程内的所有线程也就随即退出4. 线程用途合理的使用多线程能提高 CPU 密集型程序的执行效率合理的使用多线程能提高 IO 密集型程序的用户体验如生活中我们一边写代码一边下载开发工具就是多线程运行的一种表现二、Linux 进程 VS 线程 --- 哪些资源共享哪些独占进程间具有独立性线程共享地址空间也就共享进程资源2.1 进程和线程进程是资源分配的基本单位线程是调度的基本单位线程共享进程数据但也拥有自己的一部分 私有 数据:线程 ID一组寄存器线程的上下文数据栈errno信号屏蔽字调度优先级2.2 进程的多个线程共享同一地址空间因此 Text Segment、Data Segment 都是共享的如果定义一个函数在各线程中都可以调用如果定义一个全局变量在各线程中都可以访问到除此之外各线程还共享以下进程资源和环境:文件描述符表每种信号的处理方式 (SIG_IGN、SIG_DFL 或者自定义的信号处理函数)当前工作目录用户 id 和组 id进程和线程的关系如下图:3、Linux 线程控制Linux的线程控制用的是pthread库原生库所以在编译链接时需要带上“-lpthread”3.1 POSIX 线程库与线程有关的函数构成了一个完整的系列绝大多数函数的名字都是以 “pthread_” 打头的要使用这些函数库要通过引入头文pthread.h链接这些线程函数库时要使用编译器命令的 “-lpthread” 选项3.2 创建线程pthread_create 函数NAME pthread_create - create a new thread LIBRARY POSIX threads library (libpthread, -lpthread) SYNOPSIS #include pthread.h int pthread_create(pthread_t *restrict thread, const pthread_attr_t *restrict attr, void *(*start_routine)(void *), void *restrict arg); RETURN VALUE On success, pthread_create() returns 0; on error, it returns an error number, and the con‐ tents of *thread are undefined.参数thread返回线程 IDattr设置线程的属性attr为NULL表示使用默认属性start_routine是个函数地址线程启动后要执行的函数arg传给线程启动函数的参数返回值成功返回 0失败返回错误码3.3 返回自己的tidpthread_self 函数NAME pthread_self - obtain ID of the calling thread LIBRARY POSIX threads library (libpthread, -lpthread) SYNOPSIS #include pthread.h pthread_t pthread_self(void); RETURN VALUE This function always succeeds, returning the calling threads ID.代码样例#include iostream #include cstdio #include string #include unistd.h #include Task.hpp void* Rounted(void* args) { std::string name static_castconst char*(args); while(true) { printf(我是新线程%s, tid:0x%lx, pid: %d\n, name.c_str(), pthread_self(), getpid()); sleep(1); } } int main() { const int num 10; for(int i 1; i num; i) { pthread_t tid; char threadname[64]; snprintf(threadname, sizeof threadname, thread-%d, i); int n pthread_create(tid, nullptr, Rounted, threadname); (void)n; sleep(1); } while(true) { printf(我是主线程:tid:0x%lx, pid: %d\n, pthread_self(), getpid()); sleep(1); } // pthread_t tid; // int n pthread_create(tid, nullptr, Rounted,(void*)thread-1); // printf(new thread id:0x%lx\n, tid); // while(true) // { // printf(我是主线程:tid:0x%lx, pid: %d\n, pthread_self(), getpid()); // sleep(1); // } return 0; }运行代码会发现tid是一个很大是数这个数是什么呢其实是线程在虚拟地址的起始地址打印出来的tid是通过pthread库中有函数pthread_self得到的它返回一个pthread_t类型的变量指代的是调用pthread_self函数的线程的 “ID”。怎么理解这个 “ID” 呢这个 “ID” 是pthread库给每个线程定义的进程内唯一标识是pthread库维持的。由于每个进程有自己独立的内存空间故此 “ID” 的作用域是进程级而非系统级内核不认识。其实pthread库也是通过内核提供的系统调用例如clone来创建线程的而内核会为每个线程创建系统全局唯一的 “ID” 来唯一标识这个线程。使用PS命令查看线程信息zhangsanhcss-ecs-f571:~$ ps -aL PID LWP TTY TIME CMD 1747585 1747585 pts/2 00:00:00 ps-L选项打印线程信息LWP是什么呢LWP得到的是真正的线程 ID。之前使用pthread_self得到的数实际上是一个地址在虚拟地址空间上的一个地址通过这个地址可以找到关于这个线程的基本信息包括线程栈线程栈寄存器等属性。在ps -aL得到的线程 ID有一个线程 ID 和进程 ID 相同这个线程就是主线程主线程的栈在虚拟地址空间的栈上而其他线程的栈是在共享区堆栈之间因为pthread系列函数都是pthread库提供给我们的。而pthread库是在共享区的。所以除了主线程之外的其他线程的栈都在共享区。3.4 线程终止如果需要只终止某个线程而不终止整个进程可以有三种方法:从线程函数return。这种方法对主线程不适用从main函数return相当于调用exit。线程可以调用pthread_exit终止自己。一个线程可以调用pthread_cancel终止同一进程中的另一个线程。pthread_exit 函数NAME pthread_exit - terminate calling thread LIBRARY POSIX threads library (libpthread, -lpthread) SYNOPSIS #include pthread.h [[noreturn]] void pthread_exit(void *retval); RETURN VALUE This function does not return to the caller.参数retval线程结束后返回的值相当于return retval中的retvalpthread_cancel 函数NAME pthread_cancel - send a cancelation request to a thread LIBRARY POSIX threads library (libpthread, -lpthread) SYNOPSIS #include pthread.h int pthread_cancel(pthread_t thread); RETURN VALUE On success, pthread_cancel() returns 0; on error, it returns a nonzero error number.参数thread线程 ID返回值成功返回 0失败返回错误码3.5 线程等待为什么需要线程等待已经退出的线程其空间没有被释放仍然在进程的地址空间内。创建新的线程不会复用刚才退出线程的地址空间。功能等待线程结束原型int pthread_join(pthread_t thread, void **value_ptr);参数thread线程 IDvalue_ptr它指向一个指针后者指向线程的返回值返回值成功返回 0失败返回错误码调用该函数的线程将挂起等待直到id为thread的线程终止。thread线程以不同的方法终止通过pthread_join得到的终止状态是不同的总结如下:如果thread线程通过return返回value_ptr所指向的单元里存放的是thread线程函数的返回值。如果thread线程被别的线程调用pthread_cancel异常终掉value_ptr所指向的单元里存放的是常数PTHREAD_CANCELED。如果thread线程是自己调用pthread_exit终止的value_ptr所指向的单元存放的是传给pthread_exit的参数。如果对thread线程的终止状态不感兴趣可以传NULL给value_ptr参数。3.6 分离线程默认情况下新创建的线程是joinable的线程退出后需要对其进行pthread_join操作否则无法释放资源从而造成系统泄漏。如果不关心线程的返回值join是一种负担这个时候我们可以告诉系统当线程退出时自动释放线程资源。int pthread_detach(pthread_t thread);可以是线程组内其他线程对目标线程进行分离也可以是线程自己分离:pthread_detach(pthread_self());三、线程 ID 及进程地址空间布局pthread_create函数会产生一个线程 ID存放在第一个参数指向的地址中。该线程 ID 和前面说的线程 ID 不是一回事。前面讲的线程 ID 属于进程调度的范畴。因为线程是轻量级进程是操作系统调度器的最小单位所以需要一个数值来唯一表示该线程。pthread_create函数第一个参数指向一个虚拟内存单元该内存单元的地址即为新创建线程的线程 ID属于 NPTL 线程库的范畴。线程库的后续操作就是根据该线程 ID 来操作线程的。线程库 NPTL 提供了pthread_self函数可以获得线程自身的 IDpthread_t pthread_self(void);pthread_t到底是什么类型呢取决于实现。对于 Linux 目前实现的 NPTL 实现而言pthread_t类型的线程 ID本质就是一个进程地址空间上的一个地址。四、线程封装#pragma once #include iostream #include string #include functional #include pthread.h namespace myThread { static int gnumber 1; using callback_t std::functionvoid(); enum class TSTATUS { THREAD_NEW, THREAD_RUNNING, THREAD_STOP }; std::string Status2String(TSTATUS s) { switch (s) { case TSTATUS::THREAD_NEW: return THREAD_NEW; case TSTATUS::THREAD_RUNNING: return THREAD_RUNNING; case TSTATUS::THREAD_STOP: return THREAD_STOP; default: return UNKNOWN; } } std::string IsJoined(bool joinable) { return joinable ? true : false; } class Thread { private: void ToRunning() { _status TSTATUS::THREAD_RUNNING; } void ToStop() { _status TSTATUS::THREAD_STOP; } static void *ThreadRoutine(void *args) { Thread *self static_castThread *(args); pthread_setname_np(self-_tid, self-_name.c_str()); self-_cb(); self-ToStop(); return nullptr; } public: Thread(callback_t cb) : _tid(-1), _status(TSTATUS::THREAD_NEW), _joinable(true), _cb(cb), _result(nullptr) { _name New-Thread- std::to_string(gnumber); } bool Start() { int n pthread_create(_tid, nullptr, ThreadRoutine, this); if (n ! 0) return false; ToRunning(); return true; } void Join() { if (_joinable) { int n pthread_join(_tid, _result); if (n ! 0) { std::cerr join error: n std::endl; return; } (void)_result; _status TSTATUS::THREAD_STOP; } else { std::cerr error, thread join status: IsJoined(_joinable) std::endl; } } // 暂停 // void Stop() // restart() // { // // 让线程暂停 // } void Die() { if (_status TSTATUS::THREAD_RUNNING) { pthread_cancel(_tid); _status TSTATUS::THREAD_STOP; } } void Detach() { if (_status TSTATUS::THREAD_RUNNING _joinable) { pthread_detach(_tid); _joinable false; } else { std::cerr detach _name failed std::endl; } } void PrintInfo() { std::cout thread name : _name std::endl; std::cout thread _tid : _tid std::endl; std::cout thread _status : Status2String(_status) std::endl; std::cout thread _joinable : IsJoined(_joinable) std::endl; } ~Thread() { } private: std::string _name; pthread_t _tid; TSTATUS _status; bool _joinable; // 线程要有自己的任务处理即回调函数 callback_t _cb; // 线程退出信息 void *_result; }; }