欢乐狗 发表于 2024-7-20 06:32:13

Linux之线程控制

目次

对页表的再次明白(以32位为例)
线程的优点
线程的缺点
线程非常
进程VS线程
进程ID和线程ID
线程控制
POSIX线程库
创建线程
线程停止
线程期待
 分离线程
关于新线程退出结果

对页表的再次明白(以32位为例)

https://img-blog.csdnimg.cn/direct/476dde73c1f84a42a567f93e3c200e0e.png
   并不是只有一个页表, 地址的前10位对应一级页表,地址的第11-20位对应二级页表,后12位为页内偏移,其实内存和磁盘中的文件,都被分成了以4KB为单位的区域,只不外磁盘中的4KB单位叫“页帧”,内存中的4KB单位叫“页框”。

4KB = 2^12 Byte,即只要通过一级页表和二级页表找到内存中对应的页框后,根据页内偏移就能找到对应的资源。
线程在进程内部执行,是OS调理的基本单位。
https://img-blog.csdnimg.cn/direct/0120d92b3cab4fca99319f446784e78d.png
如何明白线程?
-------------------
上图中,每一个task_struct就是一个线程,赤色方框内是属于一个进程。
创建线程,不用构建新的进程地址空间,页表,以是创建线程比创建进程更轻量化。
创建线程,只必要使用主线程(原进程)的地址空间,页表等资源。
在CPU看来,并不关心执行流是进程还是线程,只认task_struct,此时,可以说Linux没有真正意义上的线程布局,是用进程task_struct模拟线程的,Linux下的进程,统称为:轻量级进程!!!
-----------------------------------------------------------------------------------------------------------------------------
如何明白进程?
--------------------
用户视角:内核数据布局+对应的代码和数据!
内核视角:负担分配系统资源的实体!
----------------------------------------------------------------------------------------------------------------------------
线程的优点

      创建一个新线程的代价要比创建一个新进程小得多;       与进程之间的切换相比,线程之间的切换必要操作系统做的工作要少很多;       线程占用的资源要比进程少很多;       能充分使用多处理器的可并行数量;       在期待慢速   I/O   操作竣事的同时,步调可执行其他的盘算使命;       盘算麋集型应用,为了能在多处理器系统上运行,将盘算分解到多个线程中实现;       I/O   麋集型应用,为了进步性能,将   I/O   操作重叠。线程可以同时期待不同的   I/O   操作。   线程的缺点

      性能损失                      一个很少被外部事件阻塞的盘算麋集型线程往往无法与共它线程共享同一个处理器。假如盘算麋集型,线程的数量比可用的处理器多,那么大概会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调理开销,而可用的资源不变。      健壮性低落                      编写多线程必要更全面更深入的思量,在一个多线程步调里,因时间分配上的眇小毛病或者因共享了不该共享的变量而造成不良影响的大概性是很大的,换句话说线程之间是缺乏保护的。      缺乏访问控制                 进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。      编程难度进步                编写与调试一个多线程步调比单线程步调困难得多
线程非常

因为创建的新线程,与主线程共用地址空间,页表等,以是新线程出现非常就会引起整个线程组非常。
      单个线程假如出现除零,野指针标题导致线程崩溃,进程也会随着崩溃。       线程是进程的执行分支,线程出非常,就类似进程出非常,进而触发信号机制,停止进程,进程停止,该进程内的全部线程也就随即退出 。     线程用途
      合理的使用多线程,能进步   CPU   麋集型步调的执行服从      合理的使用多线程,能进步   IO   麋集型步调的用户体验(如生活中我们一边写代码一边下载开辟工具,就是多线程运行的一种表现)   进程VS线程

   进程是资源分配的基本单位;    线程是调理的基本单位;    线程共享进程数据,但也拥有自己的一部分数据:      线程   ID      一组寄存器      栈      errno      信号屏蔽字      调理优先级    关于主线程和新线程对栈的使用
https://img-blog.csdnimg.cn/direct/1f525f3a54eb4a11bc35c8369e02746a.png
主线程和子线程用的栈不在同一个区域,主线程用的栈就在地址空间的栈区,但是新线程用的栈区是在pthread库中对应的区域。
那么,怎么区分各个新线程的栈?
void *threadRun(void *args)
{
    const string name = (char *)args;
    while (true)
    {
      cout<<pthread_self()<<endl;
      sleep(1);
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRun, (void *)"new thread");

    while (true)
    {
      cout << "main thread, pid: " << getpid() << endl;
      sleep(1);
    }
} 输出结果: 
https://img-blog.csdnimg.cn/direct/088ad11273dc4727b57f7bbdcd399245.png
我们发现,新线程的ID值非常大,我们在哪里见到过这么大的值那?--------虚拟地址!!!
其实否则,新线程的ID值,就是用来标识该线程在pthread库中对应的存储信息的起始位置,可以结合前边的图看到。
   进程的多个线程共享 同一地址空间,因此Text Segment、Data Segment都是共享的,假如定义一个函数,在各线程中都可以调用,假如定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:      文件描述符表      每种信号的处理方式   (SIG_ IGN   、   SIG_ DFL   或者自定义的信号处理函数   )      当前工作目次      用户   id   和组   id    主线程与新线程共用全局变量
void *threadRoutine(void *args)
{
    pthread_detach(pthread_self());

    while(true)
    {
      cout << (char*)args << " : " << g_val << " &: " << &g_val << endl;
      g_val++;
      sleep(1);
    }
    pthread_exit((void*)11);
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");

    while(true)
    {
      cout << "main thread" << " : " << g_val << " &: " << &g_val << endl;
      sleep(1);
    }
    return 0;
} 部分打印结果: (我们发现,新线程修改全局变量,主线程的也会改变,而且地址是一样的!)
 https://img-blog.csdnimg.cn/direct/ca5a751af2a94af18b4f8d993db37068.png
假如用  __thread  来修饰全局变量的话,会发现新线程修改全局变量,主线程看到的值并不会发生改变,而且二者对应的地址不同。
   在C++中,__thread是一个特定于某些编译器(如GCC)的关键字,用于声明线程局部存储(Thread-Local Storage,TLS)的变量。这意味着每个线程都有其自己的该变量的副本,而不是全部线程共享同一个变量。
当你在一个全局变量或静态变量前使用__thread修饰符时,你告诉编译器这个变量是线程局部的。每个线程在访问这个变量时,实际上是在访问它自己的私有副本,而不是共享的内存区域。
https://img-blog.csdnimg.cn/direct/087d405fd0c94b499c9ea99461764310.png
进程ID和线程ID

      struct task_struct {              ...              pid_t pid;              pid_t tgid;              ...              struct task_struct *group_leader;              ...              struct list_head thread_group;              ...      };          多线程的进程,又被称为线程组,线程组内的每一个线程在内核之中都存在一个进程描述符(   task_struct   )与之对应。进程描述符布局体中的pid   ,外貌上看对应的是进程   ID   ,其实否则,它对应的是线程   ID;进程描述   符中的   tgid   ,含义是   Thread Group ID,   该值对应的是用户层面的进程   ID。    https://img-blog.csdnimg.cn/direct/aca0961a540e427b872953a5e72a8e97.png
 熟悉进程ID和线程ID
void *threadRun(void *args)
{
    const string name = (char *)args;
    while (true)
    {}
}

int main()
{
    pthread_t tid;
    for (int i = 0; i < 5; i++)
    {
      pthread_create(tid + i, nullptr, threadRun, (void *)"");
    }

    while (true)
    {}
}
   ps命令中的-L选项,会表现如下信息:      LWP:   线程   ID   ,既   gettid()   系统调用的返回值。      NLWP:   线程组内线程的个数      

[*]-f 选项表现使用完整格式输出,包罗UID, PID, PPID, C, STIME, TTY, TIME, CMD等字段。
https://img-blog.csdnimg.cn/direct/f39b2a13385f4f63a1db0f9de3db76e6.png
https://img-blog.csdnimg.cn/direct/1e3c7aa187ab4c22875ff8b2df285b8a.png 此中,PID值和LWP值类似的是主线程,其余的都是新线程。
      pthread_ create   函数会产生一个线程   ID   ,存放在第一个参数指向的地址中。       因为线程是轻量级进程,是操作系统调理器的最小单位,以是必要一个数值来唯一表现该线程。      pthread_ create   函数第一个参数指向一个虚拟内存单位,该内存单位的地址即为新创建线程的线程   ID   ,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程   ID   来操作线程的。       线程库NPTL提供了pthread_ self函数,可以得到线程自身的ID:      pthread_t pthread_self(void);      线程控制

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   ;失败返回错误码   线程停止

   假如必要只停止某个线程而不停止整个进程,可以有三种方法:      1. 从线程函数return。这种方法对主线程不适用,从main函数return相称于调用exit。      2. 线程可以调用pthread_ exit停止自己。      3. 一个线程可以调用pthread_ cancel停止同一进程中的另一个线程。    pthread_exit函数
   
   功能:线程停止      原型      void pthread_exit(void *value_ptr);      参数              value_ptr:value_ptr不要指向一个局部变量。      返回值:无返回值,跟进程一样,线程竣事的时候无法返回到它的调用者(自身)   pthread_cancel函数
      功能:取消一个执行中的线程      原型      int pthread_cancel(pthread_t thread);      参数              thread:线程   ID      返回值:成功返回   0   ;失败返回错误码   线程期待

      功能:期待线程竣事      原型      int pthread_join(pthread_t thread, void **value_ptr);      参数              thread:线程   ID              value_ptr:它指向一个指针,后者指向线程的返回值      返回值:成功返回   0   ;失败返回错误码       调用该函数的线程将挂起期待,直到id为thread的线程停止。thread线程以不同的方法停止,通过pthread_join得到的停止状态是不同的,总结如下:      1. 假如thread线程通过return返回,value_ ptr所指向的单位里存放的是thread线程函数的返回值。      2. 假如thread线程被别的线程调用pthread_ cancel非常终掉,value_ ptr所指向的单位里存放的是常数PTHREAD_ CANCELED。      3. 假如thread线程是自己调用pthread_exit停止的,value_ptr所指向的单位存放的是传给pthread_exit的参数。      4. 假如对thread线程的停止状态不感爱好,可以传NULL给value_ ptr参数。    为什么必要线程期待?
      已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。      创建新的线程不会复用刚才退出线程的地址空间。     分离线程

      默认环境下,新创建的线程是joinable的,线程退出后,必要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。          假如不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,主动释放线程资源。      int pthread_detach(pthread_t thread);  
    可以是线程组内其他线程对目的线程进行分离,也可以是线程自己分离:      pthread_detach(pthread_self())   ;    关于新线程退出结果

void *threadRoutine(void *args)
{
    while(true)
    {
      pthread_testcancel();
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");
    int count = 0;
    while (true)
    {
      count++;
      if (count >= 5)
            break;
    }
    int n= pthread_cancel(tid);
    cout <<n<<endl;
    cout << "pthread cancel: " << tid << endl;

    int *ret = nullptr;
    pthread_join(tid, (void **)&ret); // 默认会阻塞等待新线程退出

    cout << "main thread wait done ... main quit ...: new thead quit : " << (long long)ret << "\n";

    return 0;
} 假如调用pthread_cancel来停止线程,则线程的退出码是 -1。
当然,假如新线程指定返回退出结果,可通过(void*)进行强转,然后主线程再(long long)进行强转,就可得到。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Linux之线程控制