多线程(1)---线程概念&线程控制

一、线程概念:

1、概念:

线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
在UnixSystem V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。
线程是独立调度和分派的基本单位。线程可以为操作系统内核调度的内核线程,如Win32线程;由用户进程自行调度的用户线程,如Linux平台的POSIX Thread;或者由内核与用户进程,如Windows 7的线程,进行混合调度。
同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。 一个进程可以有很多线程,每条线程并行执行不同的任务。
在多核或多CPU,或支持Hyper-threading的CPU上使用多线程程序设计的好处是显而易见,即提高了程序的执行吞吐率。在单CPU单核的计算机上,使用多线程技术,也可以把进程中负责I/O处理、人机交互而常被阻塞的部分与密集计算的部分分开来执行,编写专门的workhorse线程执行密集计算,从而提高了程序的执行效率。
----来自百度百科

>在这里插入图片描述

2、线程与进程的关系与区别(Linux操作系统下)

(1) 进程是系统资分配的基本单位,线程是CPU调度的基本单位;
(2) 一切进程至少都有一个执行线程,线程在进程内部运行,本质是在进程地址空间内运行;
(3) CPU看到的PCB都要比传统的进程更加轻量化透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流;
(4) 创建一个新线程的代价要比创建一个新进程小得多,与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多,线程占用的资源要比进程少很多;
(5) 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出;
(6) 在多任务处理时,都能并行压缩CPU处理/IO等待时间。

3、线程间的独有与共享

(1)独有:栈,寄存器,信号屏蔽字,errno,标识符;
(2)共享:虚拟地址空间(代码段,数据端),文件描述符表,信号处理方式,当前工作目录路径,用户 ID 和组 ID; ( 因为共用同一虚拟地址空间,所以线程间通信更加方便。)

4、多进程和多线程进行多任务处理

(1)多线程

  • 线程间通信更加方便灵活;
  • 线程的创建和销毁成本更低;
  • 线程间的调度成本更低;
  • 线程的执行粒度更加细致;
  • 异常和某些系统调用针对是针对整个进程;

(2)多进程

  • 具有独立性,因此更加稳定,健壮;

所以说,多线程和多进程没有谁好谁坏,得看使用情况,例如:对主功能程序安全性稳定性要求更高的最好使用多进程(shell/服务器)。

二、线程控制

线程控制的接口都是库函数,操作系统并没有向用户提供创建一个轻量级进程的接口,因此大佬们才封装了一套线程的控制接口。
Compile and link with -pthread.编译和链接时使用 -lpthread / -pthread

1、线程创建:

#include <pthread.h>
1.int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
//创建一个线程
//thread---用于获取线程id,通过这个id可以找到线程的描述信息,进而访问PCB
//pthread_t是一个unsigned long int型的数据,thread保存的是创建的这个线程在共享区中的独有的信息的地址
//attr---线程属性,通常置 NULL,
//start_routine---线程的入口函数,创建一个线程就是为了运行这个函数,函数运行完毕,线程退出
//arg---通过线程的入口函数传递给线程的参数
//返回值---成功返回 0; 失败返回一个非 0值(errno)
2.命令
ps -efL ---查看线程信息
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>

void* start_routine(void* arg)//线程要运行的函数
{
    while(1)
    {
        printf("recive main thread :%s\n",(char*)arg);
        sleep(1);
    }
    return NULL;
}

int main()
{
    pthread_t tid;//pthread_t == unsigned long int
    char buf[] = "这是一种个测试线程创建的demo~~\n";
    
    int ret = pthread_create(&tid,NULL,start_routine,(void*) buf);//创建线程

    if(ret != 0)//创建失败返回一个非0值
    {
        printf("thread create error:%d\n",ret);
        return -1;
    }

    while(1)//主线程
    {
        printf("this is main thread,child thread tid=%p\n",tid); 
        sleep(1);
    }
    return 0;
}

****加粗样式****
在这里插入图片描述

说明:
(1)程序结果中的tid打印的是一个地址,那个地址是这个线程在共享区中保存的描述信息,参考上面第一幅图
(2)在每个PCB中都会有PID 和TGID

  • PID:线程ID,对应ps -efL查看结果中的LWP
  • TGID :线程组ID,也就是这个进程的ID,对应ps -efL查看结果中的PID

2、线程终止:

(1)通过线程入口函数的return,终止一个线程;
(2)通过库函数接口终止;
注意:

  • main函数中return是退出进程
  • 在线程入口函数的任意位置调用线程终止库函数都会终止这个线程,跨栈操作也支持。
#include <pthread.h>
1.void pthread_exit(void* retval);
//退出一个线程,谁调用,谁退出
//retval---线程返回值
2.void pthread_cancel(pthread_t tid);
//退出指定线程,通过线程的tid
3.pthread_t pthread_self();
//获取当前线程的tid

注意:
(1)线程退出,也不会完全释放资源(类似于僵尸进程,但没有僵尸线程这种说法),需要其他线程等待;
(2)线程可以取消自己(pthread_cancel),但这是违规操作,会出现一定的逻辑混乱;
(3)主线程退出,其他线程正常运行,这不是一个主流做法;
(4)主线程退出,并不影响整个进程的运行,只有所有线程退出,进程才会退出。

3、线程等待:

等待一个线程的退出,获取退出线程的返回值,回收这个线程所占的资源。
不是所有的线程都能被等待,一个线程创建时,默认情况下有一个属性–joinable,处于joinable属性的线程退出后,不会释放资源,需要被等待;

#include <pthread.h>
1.int pthread_join(pthread_t tid,void** retval);
//等待指定线程(tid)的退出,并获取他的返回值
//tid---指定要等待线程的值
//retval---用于获取线程退出的返回值

4、线程分离:

将线程的属性从joinable设置为deatch;处于detach属性的线程退出后会自动释放资源,不需要被等待;
注意:线程分离未必就比线程等待好,应用场景不同。

int pthread_detach(pthread_t tid);
//分离指定线程;
//tid---线程tid
//返回值---成功返回 0,失败返回非 0值errno
//被分离的线程不能被等待,error:这不是一个joinable线程;
//因为在获取返回值的时候,detach属性的线程退出后已经自动释放了资源。

线程的分离可以再任意地方,可以在线程入口函数中让线程分离自己,也可以让创建之后直接分离。

写点代码练一下:

//create.pthread
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>

void* start_routine(void* arg)//线程入口函数
{
    char* retval = "这个线程退出了~~\n";
   //pthread_detach(pthread_self());//分离线程方式2,在线程入口函数出分离  
    printf("recive main thread :%s\n",(char*)arg);
    
    sleep(3);
    pthread_exit(retval);//线程退出方式2;
  //pthread_cancel(pthread_self());//线程退出方式3
    
    return NULL;//线程退出方式1,
}

int main()
{
    pthread_t tid;//pthread_t == unsigned long int
    char buf[] = "这是一种个测试线程创建的demo~~\n";
    void *retval = NULL;

    int ret = pthread_create(&tid,NULL,start_routine,(void*) buf);//创建线程
    //pthread_detach(tid);//分离线程方式1,创建完线程后分离

    if(ret != 0)//创建失败返回一个非0值
    {
        printf("thread create error:%d\n",ret);
        return -1;
    }
    
    while(1)
    {
      if(!pthread_join(tid,&retval))//线程等待
      {
        printf("等待线程退出~~\n");
      }

     else
      {
          printf("线程已退出,retval = %s\n",retval);
          break;
      }

    }
    while(1)//主线程
    {
        printf("this is main thread,child thread tid=%p\n",tid);
        sleep(1);
    }

    return 0;
}

在这里插入图片描述

下一篇:线程安全:点这里

发布了77 篇原创文章 · 获赞 16 · 访问量 6503

猜你喜欢

转载自blog.csdn.net/weixin_43886592/article/details/104027571