Linux-线程终止-线程等待-线程分离-线程安全

Posted 天津 唐秙

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux-线程终止-线程等待-线程分离-线程安全相关的知识,希望对你有一定的参考价值。

1. 线程终止

1.1 pthread_exit函数

在这里插入图片描述
  void pthread_exit(void *retval);

作用: 谁调用谁退出
参数:
  retval:线程A结束的时候传递给等待线程B的参数
验证:

#include <stdio.h>    
#include <unistd.h>    
#include <pthread.h>    

#define a 4    

struct ThreadId
{
    int thread_id_;
};                                                                      

void* MyThreadStart(void* arg)    
{    
    struct ThreadId* ti = (struct ThreadId*)arg;    

    printf("i am MyThreadStart, i = %d\\n", ti->thread_id_);    
    sleep(1);    

    pthread_exit(NULL);    
    printf("pthread_exit, i = %d\\n", ti->thread_id_);    

    delete ti;    
}

int main()
{
    pthread_t tid;

    for(int i = 0; i < a; i++)
    {
        struct ThreadId* ti = new ThreadId();
        ti->thread_id_ = i;

        int ret = pthread_create(&tid,NULL,MyThreadStart,(void*)ti);
        if(ret < 0)
        {
            perror("pthread_create");
            return 0;
        }
    }

    //创建成功
    while(1)
    {
        printf("i am main thread\\n");
        sleep(1);
    }

    return 0;                                                           
}

在这里插入图片描述
  如果线程退出,打印如图所示,如果线程未退出,每一个线程会打印两句话。

1.2 线程的入口函数代码执行完毕,线程退出

1.3 pthread_cancel函数

在这里插入图片描述
  int pthread_cancel(pthread_t thread);

功能: 根据线程的标识符终止对应线程
参数:
  thread:被终止的线程的标识符
获取自己的线程标识符: pthread_t pthread_self(void);
在这里插入图片描述
验证:

#include <stdio.h>    
#include <unistd.h>    
#include <pthread.h>    
  
#define THREAD_NUM 4    
  
struct ThreadId    
{    
    int thread_id_;    
};    
  
void* MyThreadStart(void* arg)    
{    
    struct ThreadId* ti = (struct ThreadId*)arg;    
    //while(1)    
    {    
        printf("i am MyThreadStart, i = %d\\n", ti->thread_id_);    
        sleep(1);    
    }    
    pthread_cancel(pthread_self());                                   
  
    delete ti;    
}  

int main()
{
    pthread_t tid[THREAD_NUM];

    for(int i = 0; i < THREAD_NUM; i++)
    {
        struct ThreadId* ti = new ThreadId();
        ti->thread_id_ = i;

        int ret = pthread_create(&tid[i], NULL, MyThreadStart, (void*)  ti);
        if(ret < 0)
        {
            perror("pthread_create");
            return 0;
        }
    }

    //pthread_cancel(pthread_self());
    while(1)
    {
        printf("i am main thread\\n");
        sleep(1);
    }                                                                 

    return 0;
}

在这里插入图片描述
在这里插入图片描述
  如果主线程自己取消自己,调用pthread_cancel(pthread_self),那么主线程的状态变为僵尸状态(Z),工作线程正常,整个进程没有退出。

验证:

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

#define THREAD_NUM 4
                                                                    
struct ThreadId
{
    int thread_id_;
};

void* MyThreadStart(void* arg)
{
    struct ThreadId* ti = (struct ThreadId*)arg;
    while(1)
    {
        printf("i am MyThreadStart, i = %d\\n", ti->thread_id_);
        sleep(1);
    }
    delete ti;
}

int main()
{
    pthread_t tid[THREAD_NUM];

    for(int i = 0; i < THREAD_NUM; i++)
    {
        struct ThreadId* ti = new ThreadId();
        ti->thread_id_ = i;

        int ret = pthread_create(&tid[i], NULL, MyThreadStart, (void*)ti);
        if(ret < 0)
        {
            perror("pthread_create");
            return 0;
        }
    }

    pthread_cancel(pthread_self());
    while(1)
    {
        printf("i am main thread\\n");
        sleep(1);                                                       
    }

    return 0;
}

在这里插入图片描述
在这里插入图片描述

2. 线程等待

2.1 原因

  由于线程的默认属性为joinable属性,当线程退出的时候其资源不会被操作系统回收,需要其他线程来进行进程等待,继续回收,否则就会造成内存泄露。

2.2 接口

  int pthread_join(pthread_t thread, void **retval);
在这里插入图片描述
  thread:需要等待的线程的标识符
  retval:线程退出时的返回值

  1.线程入口函数退出的时候,retval就是线程入口函数的返回值
  2.pthread_exit(void* retval),retval就是pthread_exit函数的参数值
  3.pthread_cancel:retval的值是一个常数PTHREAD_CANCELED,调用pthread_join函数进行等待的执行流如果还没有等待到退出的线程,则当前调用pthread_join函数的执行流就会阻塞。

2.3 验证

#include <stdio.h>                                                      
#include <unistd.h>    
#include <pthread.h>    

#define THREAD_NUM 4    

struct ThreadId    
{    
    int thread_id_;    
};    

void* MyThreadStart(void* arg)    
{    
    struct ThreadId* ti = (struct ThreadId*)arg;    
    while(1)    
    {    
        printf("i am MyThreadStart, i = %d\\n", ti->thread_id_);    
        sleep(1);    
    }    
    delete ti;    

    return NULL;    
}    

int main()
{
    pthread_t tid[THREAD_NUM];

    for(int i = 0; i < THREAD_NUM; i++)
    {
        struct ThreadId* ti = new ThreadId();
        ti->thread_id_ = i;

        int ret = pthread_create(&tid[i], NULL, MyThreadStart, (void*)ti);
        if(ret < 0)
        {
            perror("pthread_create");
            return 0;
        }
    }

    for(int i = 0; i < THREAD_NUM; i++)
    {
        pthread_join(tid[i], NULL);
    }

    while(1)                                                            
    {
        printf("i am main thread\\n");
        sleep(1);
    }
    return 0;
}

在这里插入图片描述
在这里插入图片描述

3. 线程分离

3.1 概念

  一个进程的属性如果从joinable属性变成detach属性,则当前这个线程在退出的时候,不需要其他线程回收资源,操作系统会自己回收资源。

3.2 接口

在这里插入图片描述
  int pthread_detach(pthread_t thread);

参数: pthread_t thread
  如果工作线程自己分离自己,要写在工作线程代码的最前面。

3.3 验证

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

#define THREAD_NUM 4

struct ThreadId
{
    int thread_id_;
};                                                                      

void* MyThreadStart(void* arg)
{
    pthread_detach(pthread_self());
    struct ThreadId* ti = (struct ThreadId*)arg;
    //while(1)
    {
        printf("i am MyThreadStart, i = %d\\n", ti->thread_id_);
        sleep(1);
    }
    delete ti;

    return NULL;
}

int main()
{
    pthread_t tid[THREAD_NUM];

    for(int i = 0; i < THREAD_NUM; i++)
    {
        struct ThreadId* ti = new ThreadId();
        ti->thread_id_ = i;

        int ret = pthread_create(&tid[i], NULL, MyThreadStart, (void*)ti);
        if(ret < 0)
        {
            perror("pthread_create");
            return 0;
        }
    }

    while(1)
    {
        printf("i am main thread\\n");
        sleep(1);
    }

    return 0;                                                           
}

在这里插入图片描述在这里插入图片描述

4. 线程安全

4.1 概念

  多个执行流,访问临界资源,不会导致程序产生二义性。
    执行流: 理解为线程
    访问: 指的是对临界资源进行操作
    临界资源: 指的是多个线程都可以访问到的资源
      例如:全局变量,某个结构体变量,某个类的实例化指针
    临界区: 代码操作临界资源的代码区域称之为临界区
    二义性: 结果会有多个

4.2 原理

在这里插入图片描述
  正常来说,第一步,内存当中有一个i = 10,寄存器从内存中读到i = 10,第二步,CPU从寄存器中拿到i的值进行计算,第三步,CPU将计算的结果写回到寄存器,第四步,寄存器将写回的值在内存中进行修改。

4.3 线程不安全的现象

  内存中我们有一个i = 10,我们假设有两个线程,两个线程都是对于i进行加1操作。线程1先开始执行,当线程1把i读到寄存器后,由于计算机是分时轮转,这时候时间片用完了,线程1被切换出去了,此时线程1在寄存其中i的值是10,之后线程2被切换进来了,线程2从内存当中读到i的值为10,经过CPU进行运算,写回到寄存器,寄存器再存到内存当中,这时候i在内存当中的值被修改为了11,线程2结束后,线程1又被切换回来了,线程1通过保存的上下文信息知道i此时的值是10,通过程序计数器知道要在CPU当中进行加1操作,经过CPU进行运算,写回到寄存器,寄存器再存到内存当中,这时候i在内存当中的值仍然是11,但是实际上我们对i执行了两次加1操作,正确的值是12,这时候代码的结果就存在了二义性。

4.4 验证

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

#define THREAD_NUM 2

int g_tickets = 10;


void* MyThreadStart(void* arg)
{
    while(1)
    {
        if(g_tickets > 0)
        {
            printf("i have %d, i am %p\\n", g_tickets, pthread_self());
            g_tickets--;
        }
        else
        {
            pthread_exit(NULL);
        }
    }
    return NULL;
}

int main()
{
    pthread_t tid[THREAD_NUM];
    for(int i = 0; i < THREAD_NUM; i++)
    {
        int ret = pthread_create(&tid[i], NULL, MyThreadStart, NULL);
        if(ret < 0)
        {
            perror("pthread_create");
            return 0;
        }
    }

    for(int i = 0; i < THREAD_NUM; i++)
    {
        pthread_join(tid[i], NULL);
    }

    printf("pthread_join end...\\n");
    return 0;
}

以上是关于Linux-线程终止-线程等待-线程分离-线程安全的主要内容,如果未能解决你的问题,请参考以下文章

Linux:详解多线程(线程概念线程控制—线程创建线程终止线程等待)

Linux多线程

linux--线程

[linux] linux多线程详解

[linux] linux多线程详解

Linux入门多线程(线程概念生产者消费者模型消息队列线程池)万字解说