线程

线程的概念:

1. 什么是线程?

百度百科给出的定义太长了,总结的来说:一个线程就是一个执行流。
我们了解到程序运行后,就会变成进程,而一个进程中执行的路线就称为一个执行流(线程),更明确的定义是:线程是一个进程内部的控制序列。

因为Linux中没有线程的概念,所以Linux下的线程称为“轻量级进程”。

2. 线程和进程的关系
进程是内存资源分配的最小单位,
线程是调动的最小单位。
图解:

这里写图片描述

由此图我们了解到,一个程序中有多个函数,假设每个函数执行时间为1,那么整个程序的执行时间是5。而我们如果引入线程的概念,实每个线程负责一个函数,那我们最终的执行时间为2。这样就大大减少了程序的时间,提高了效率。(当然这是理想状态,其中还涉及到线程的切换什么,调度算法什么。)总的来说,引入线程,会处理许多进程不方便完成的事情。

我们知道进程中的创建子进程,资源共享,数据写时拷贝。那线程也一样吗?

我们从线程的概念中了解到:

  • 有一部分资源共享(数据和资源,文件描述符表,当前的工作目录,用户id和组id)
  • 有属于自己的独立资源(线程ID,寄存器,私有栈,信号屏蔽字,调度优先级(上下文))

线程的优缺点:

1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
3)调度和切换:线程上下文切换比进程上下文切换要快得多。
4)在多线程OS中,线程不是一个可执行的实体。

线程控制:

因为Linux中没有线程的概念,所以对应的就没有了线程的函数和库。所以我们就得引入POSIX线程库。
线程的创建:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);
 //thread:返回线程id
 //attr:设置线程属性
 //start_routine:线程创建完成时所执行的函数
 //arg:传给线程执行函数的参数
//返回值:0为成功,失败返回错误码

下面来验证一下

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

void *fun()
{
    while(1)
    {
        printf("|||||||||||||||||||||||\n");
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    int ret = pthread_create(&tid,NULL,fun,NULL);
    if(ret != 0)
    {
        printf("prhread_create error!\n");
        exit(-1);
    }
    while(1)
    {
        printf("------------------------\n");
        sleep(1);
    }
    return 0;
}

在上面的代码中我们清楚的看到有俩个函数,每个函数中都有一个死循环,并且main函数中没有调用这个函数。按照正常来推断,我们最终得到是main函数内的死循环。可是结果呢?
这里写图片描述

从实验效果我们可以推测到,这个创建线程一定是在进程内部增加了一条执行流。分别执行各自的代码片段。

进程和线程的ID:

  • 在Linux中因为没有线程的概念,所以Linux中关于线程的描述是“轻量级进程”,而在操作系统眼中,对于线程的描述和组织也是采用:task_struct,意味着线程也有自己的进程描述符。
  • 没有线程以前,一个进程对应一个进程描述符,即使创建子进程也是如此。但是引入了线程的概念之后,我们明白线程也是需要拥有自己的进程描述符。那么在内核的眼中一个进程中对应N个进程描述符,在控制中不会产生错误吗?
    这里写图片描述
    这是我们上面测试的例子,执行中我们查看当前系统的线程状态,发现他们俩的PID相同,不同的只是LWP。那么LWP是什么呢? 是线程ID,即gettid()的返回值。

那么在一个进程中拥有多个线程的进程称为什么呢? 这就引入了多线程的概念:线程组。

struct task_struct 
{
 .
 .
 .
 int pdeath_signal;     //父进程终止是向子进程发送的信号
 unsigned long personality;
 //Linux可以运行由其他UNIX操作系统生成的符合iBCS2标准的程序
 int did_exec:1; 

pid_t pid;
==========

  //进程标识符,用来代表一个进程
 pid_t pgrp;   //进程组标识,表示进程所属的进程组
 pid_t tty_old_pgrp;  //进程控制终端所在的组标识
 pid_t session;  //进程的会话标识

pid_t tgid;
===========

 int leader;     //表示进程是否为会话主管
 struct task_struct *p_opptr,*p_pptr,*p_cptr,*p_ysptr,*p_osptr;
 struct list_head thread_group;   //线程链表
 struct task_struct *pidhash_next; //用于将进程链入HASH表
 struct task_struct **pidhash_pprev;
 wait_queue_head_t wait_chldexit;  //供wait4()使用
.
.
.
 };

从上面的结构体中来看,有pid_t pid , pid_t tgid俩个 ,那么这和线程又有什么关系呢? 其中pid呢代表的是每个独立的线程的线程id,而tgid呢是指在用户层面上的进程ID,相当于一个线程组中tgid相等(相当于这个进程的id),而pid呢是每个线程的id。

获取线程ID:

#include <sys/syscall.h>

pid_t tid;
tid = syscall(SYS_gettid);

线程终止:

  • 线程的调用函数中执行return。但是在主线程中不适用这种,主线程return相当于进程退出
  • 线程可以自己调用pthread_exit来结束自己
  • 其他进程可以调用pthread_cancel来结束其他的进程
 #include <pthread.h>

 void pthread_exit(void *retval);
//无返回值
#include <pthread.h>

int pthread_cancel(pthread_t thread);
//返回值:成功为0,失败返回错误码

线程的等待与分离 :

我们都知道进程需要等待,如果不等待的话,就会造成僵尸进程,从而引发内存泄露。所以线程也必须要进行等待。

int pthread_join(pthread_t thread,void **value_prt);
//thread:线程ID
//value_prt:指向一个指针,指针指向线程的而返回值
//返回值:成功为0,失败返回错误码。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/syscall.h>

void *fun()
{
    int i = 5;
    pid_t tid;
    tid = syscall(SYS_gettid);
    while(i--)
    {
        printf("|||%d\n",tid);
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    int ret = pthread_create(&tid,NULL,fun,NULL);
    void *rt;

    if(ret != 0)
    {
        printf("prhread_create error!\n");
        exit(-1);
    }
    pthread_join(tid,&rt); 
    printf("新线程退出\n");
    return 0;
}

这里写图片描述

线程的分离:

默认情况下,线程是需要join的,意思是线程退出后,必须要进行pthread_join操作,如果不进行join操作,线程就无法释放其资源,造成内存泄露。

但是当我们不关心一个线程的返回值,join是一种浪费。所以我们将该线程从线程组分离出去,相当于告诉系统,这个线程退出直接释放线程资源,不需要等待。

int pthread_detach(pthread_t thread);

一个线程可以是其他线程使其分离,也可以是自己使自己分离。

joinable和分离是冲突,一个线程不能既joinable,又分离。

测试代码:

void* fun()
{
    //pthread_detach(pthread_self());
    printf("111\n");
    sleep(1);
    printf("xiancheng quit!\n");
    return NULL;
}

int main()
{
    int i = 0;
    while(1)
    {
        i++;
        pthread_t tid;
        int ret = pthread_create(&tid,NULL,fun,NULL);
        if(ret != 0)
        {
            printf("exit :%d   i = %d\n",ret,i);
            return 0;
        }
        usleep(99);
    }
    return 0;
}

我们先来测试一下没有调pthread_join函数无限创建新线程。来看下效果:

这里写图片描述

看明白了吗? 如果线程没有调用pthread_join函数,那么退出的新线程的资源就不会回收,而我们可以清晰地看到,整个代码执行过程中我们共创建了32755个新线程,在创建32766个线程发现内存空间不足了,所以返回创建线程失败的错误码。

那我们要是每创建一个线程,就调用pthread_detach函数会是什么效果呢?

这里写图片描述

我们可以清楚的看到只是调用线程函数中进行了线程分离,而创建线程的个数就大大增加了。
这下应该能体会到了等待和分离的作用了吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值