目录
1.线程相关性质和操作
(1)LWP的查看
(2)资源划分
①时间资源
②共享资源
2.线程封装
(1)封装后的效果
(2)类的设计及其实现
①成员变量的设计
②成员函数的设计
(3)全部代码
1.线程相关性质和操作
(1)LWP的查看
当创建了多个线程后,我们希望在bash中查看这些线程的属性。
我们发现ps -ajx只能以进程的尺度来查看,想要得到轻量级进程的相关属性,需要使用ps -aL
这个时候我们就能查看到当前系统的LWP属性了。PID自不必说,就是进程的id,用于标识唯一的进程。上图所示两个都是同一进程下的LWP。我们接着看LWP,469053和469054,显然前者为主线程,后者是新线程,主执行流的LWP和其PID相同。但有个问题,这些LWP值和左侧打印的线程的id值没有关系啊,这是为什么?
在Linux里面没有线程,只有轻量级进程,pthread进行了上层封装向我们提供接口以使用线程。不仅如此,所有线程的属性也都做了上层封装,在struct pthread里面,由pthread.so维护,这是上篇文章提到的。而这里是以系统层级查看LWP,这个值和pthread上层封装后的线程id肯定是不一样的。在系统层面根本没有线程,也不存在线程的属性,我们pthread_self()打印的值是库维护的,而不是系统的,因此这个LWP和线程id值不一样。
我们还能发现主执行流的LWP和PID的值一致,新线程的LWP是紧接着的数字。这说明OS调度看到的其实是LWP,PID其实是第一个主执行流的LWP的值,我们之前因为都是单线程没发现这一点。对于OS来说,区分执行流的是LWP而不是PID,PID从LWP中来,作为标识整个进程的id。当然,task_struct里面肯定也存着PID和LWP,标识这该执行流属于那个进程。对于单线程来说,LWP == PID。
只要彻底理解了Linux下线程的概念,这些值的分配就很好理解了。
(2)资源划分
①时间资源
对于Linux来说,新线程和主线程谁先运行不确定。另外,线程会瓜分进程的时间片资源,但不会为线程多争取时间片资源。这意味着给一个进程的时间片始终是恒定的,不会因为增加线程的数量而增加进程的时间片,不然就会发生通过增加线程数来获取时间的混乱。
②共享资源
不同线程可重入一个函数,即多个线程同时进入函数。如果是打印操作,那么打印时就会发生混乱,因为多线程访问公共的显示器文件会发生数据不一致、竞争的问题。在这种情况下,显示器文件就是共享资源,需要同步等操作来保护,后续会专门展开讲解。
2.线程封装
只要理解了线程控制的基本操作,基本的线程封装就很快能写出来了,我简要讲解思路即可。
(1)封装后的效果
线程封装不难,但一定要搞清楚为什么封装?封装后有什么特性?这个thread类在整个封装中有什么功能和作用?实例化的对象充当什么角色?这个对象是如何管理线程的?这结合了C++和Linux系统的某些思想,需要好好体会。
C++是一个面向对象的语言。我们使用类封装线程的控制后,希望一个对象即可管理一个新线程。我们只需要调取它的成员函数就可以对这个新线程操作。这就像先描述、再组织那样,对一个线程的管理转为对一个实例化对象的管理。
下面是封装完后的整体简单使用
(2)类的设计及其实现
①成员变量的设计
在设计中,我们秉承着用一个对象来管理一个线程的思想。这就像先描述、再组织那样,要存储线程相关的属性,包括线程名字_name,它正处于的状态_state等。注意这个类只是描述线程的,它只是线程外部管理的壳子。
其状态和函数包装器的设计如下
我们这次的简单设计只考虑无参的函数,重点在于体会封装的过程。
②成员函数的设计
对于构造函数,就是对线程的各种属性先做初始化。注意这个时候线程实际上还没有被创建出来。我们一定不能搞混,线程封装的对象是用来管理、描述线程的,这个对象本身不是一个新线程,因此创建这个新对象不意味着已经创建了一个新线程。很多对线程封装感到别扭就是在这里理解有误。
start函数这里才算是真正创建了线程。其原理是用户拿着对象进行t.start()时,这个对象调用成员方法创建一个新线程,并且将这个新线程的tid自动保存在成员变量里,以便后续维护。对于用户而言,它无需知道这个新线程的tid即可管理线程,因为这个类已经帮我们管理起来了,仔细体会这层封装。并且我们要知道这个对象调用其成员函数的整个过程都是在主线程中执行的,只是在类域里面执行函数,创建线程还是算作主线程创建的。
对于join函数来说,就是要让当调用t.join()时,让调用方主线程等待新线程,同时更新_joinable到属性里面防止被join两次。
当main函数那里调用t.join时,主线程会到类域里面来执行函数,所以实际上是主线程调用的pthread_join函数,是主线程被阻塞等待,整个过程中类不会对线程的执行产生什么影响。
其余代码都很简单,最后展示
(3)全部代码
#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>
#include <functional>using namespace std;namespace myThread
{static int count = 1;enum STATE{INIT,RUNNING,CANCEL,FINISH};class thread{using func_t = function<void()>;static void* execute(void* p){static_cast<thread*>(p)->_fun();return nullptr;}public:thread(void(*pfun)()):_state(INIT),_tid(-1),_fun(pfun),_joinable(true){ _name = "thread-" + to_string(myThread::count++);printf("对象已创建\n");}bool start(){if(_state == INIT){pthread_create(&_tid, nullptr, execute, (void*)this);_state = RUNNING;printf("0x%lx线程已开始运行\n", _tid);return true;}printf("0x%lx线程已被创建,不能重复启动\n", _tid);return false;}bool joinable(){return _joinable;}bool join(){if(_joinable){if(pthread_join(_tid, nullptr) == 0){printf("0x%lx线程已被join,返回值已自动抛弃\n", _tid);_joinable = false;_state = FINISH;return true;}}printf("join失败\n");return false;}bool cancel(){if(_state == RUNNING){if(pthread_cancel(_tid) == 0){printf("0x%lx线程已被cancel\n", _tid);_state = CANCEL;return true;}}printf("cancel失败\n");return false;}bool detach(){if(_state == RUNNING){if(pthread_detach(_tid) == 0){printf("0x%lx线程已分离\n", _tid);return true;}}printf("detach失败\n");return false;}private:string _name;myThread::STATE _state;pthread_t _tid;func_t _fun;bool _joinable;};}