【线程】概念与控制

线程概念与控制、线程分离、

一、线程的概念

1.概念
在一个程序里的一个执行流就叫做线程,是一个进程内部的控制序列。线程是调度的基本单位,在Linux下,线程称为轻量级进程。
2.线程与进程之间的区别
1)进程是承担系统分配资源的基本单位,线程是CPU调度的基本单位。
2)一个线程只能属于一个进程,并且线程共享整个进程的资源,一个进程可以拥有多个线程,但至少拥有一个线程。
3)线程是轻量级进程,比进程的创建、维护、管理的负担小。
4)线程没有地址空间,线程包含在进程的地址空间内。
5)线程的创建使用pthread_create,进程的创建使用fork()或vfork()。
6)进程之间的通信需要特别的方法,而线程之间可以直接读写进程数据段来进行通信—–需要进程同步和互斥手段的辅助、保持数据的一致性。
7)进程ID使用pid_t来表示,线程ID使用pthread_t来表示。
3.线程间共享
一个进程的所有信息对该进程的所有线程都是共享的,包括可执行程序的代码、程序的全局内存和堆内存、栈以及文件描述符。所有线程线程都具有相同的地址空间
4.线程拥有自己的一部分数据
线程ID、私有栈、独立的上下文数据、一组寄存器、信号屏蔽字。
5.线程的优点
创建一个新线程的代价要比创建一个新进程小。
多个线程自动的可以访问相同的存储地址空间和文件描述符。
线程占用的资源要比进程少很多。
6.线程的缺点
编写与调试一个多线程程序比单线程程序困难的多。

补充
线程是CPU调度的基本单位,所以线程之间CPU资源不共享。
线程不拥有系统资源,只拥有一点儿在运行中必不可少的资源。
线程状态:新线程态、可运行态、阻塞态、死亡态、就绪态。
线程的基本组成:程序、数据、线程控制表(TCB)、

二、线程的控制

1.线程创建

int pthread_create(pthread_t *thread,const pthread_attr_t *attr,\
void *(*start_routine)(void*),void *arg);

//参数
thread:返回线程ID
attr:设置线程的属性,为NULL表示默认属性。
start_routine:是一个函数地址,线程启动后要执行的函数。
arg:传给线程启动函数的参数。
//返回值
成功返回零,失败返回错误码。
//错误检查
对于pthread函数的错误,建议通过返回值判定,因为读取返回值要比读取线程内的error变量开销小。

创建实例:

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

void *rout(void* arg)
{
    while(1)
    {
        printf("i am a new pthread = %p\n",pthread_self()); 
        //查看自身线程ID:
        //函数返回pthread_t类型的,只有成功返回线程ID,没有失败返回值
        sleep(1);
    }
}
int main()
{
    pthread_t tid;
    pthread_t ret;
    ret=pthread_create(&tid,NULL,rout,NULL);
    if(ret!=0)
    {
        printf("pthread_create:%s\n",strerror(ret));
        exit(EXIT_FAILURE);
    }
    while(1)
    {
        printf("i am a main pthread=%p\n",pthread_self());
        sleep(1);
    }
    return 0;
}

实例结果:
这里写图片描述
注:在编译连接的时候,需要引入线程函数库 -lpthread。
这里写图片描述
1)PID与LWP相同的为主线程。
2)多线程的进程,又被称为线程组,线程组内的每一个线程在内核之中都存在一个进程描述符(task_struct)与之对应。进程描述符结构体中的pid,表面上看对应的是进程ID,其实不然,它对应的是线程ID;进程描述符中的tgid,含义是Thread Group ID,该值对应的是用户层面的进程ID。

struct task_struct{
    ...
    pid_t pid;  //线程ID, 系统调用:pid_t gettid(void);
    pid_t tgid;  //用户层面进程ID, 系统调用:pid_t getpid(void); 
    struct list_head thread_group;  //双链表,将所有线程连接在一起。
    ...
}

3)在线程组里面,所有线程都是对等的关系,没有像进程有父进程的概念。
4)pthread_t类型
pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址。

2.线程终止
如果进程中的任意线程调用了exit、_exit、那么整个进程就会终止。
如果只需要终止某个线程,而不是终止整个进程,有三种方法。
①从线程函数return,这种方法对主线程不适用。
②线程调用pthread_exit终止自己。

void pthread_exit(void *value_ptr);
//参数不要指向一个局部变量。

③线程被同一进程的其他线程请求终止。

#include <pthread.h>
int pthread_cancel(pthread_t tid);
//参数是线程ID
//返回值:成功返回零,否则,返回错误编号。

三、线程的等待与分离

1.线程的等待
1)为什么进行线程等待?
已经退出的线程,其空间并没有被释放,仍然在进程的地址空间内,所以需要等待进行回收,防止内存泄漏。
2)线程等待函数

//等待线程结束
int pthread_join(pthread_t thread,void **value_retval);
//参数:
thread:线程ID
value_retval:它指向一个指针,后者指向一个线程的返回值
//返回值:
成功返回零,失败返回错误编号。

调用该函数的线程将挂起等待,直到ID为pthread的线程终止,通过不同的终止方法,pthread_join得到的终止状态是不同的。有以下四种情况。

  1. 如果是使用return进行线程终止的,value_retval所指向的单元存放的是thread线程函数的返回值。
  2. 如果是使用pthread_exit进行自我终止的(在等待线程的内部调用pthread_exit函数),value_retval所指向的单元存放的是传给pthread_exit的参数。
  3. 如果是被别的线程使用pthread_cancel终止的,value_retval所指向的单元存放的是常数PTHREAD_CANCELED。它其实是一个宏,值为-1。
  4. 如果对此函数的终止状态不感兴趣,可以给参数传NULL。

2.线程的分离
1)为什么存在线程的分离?
默认情况下,新创建的线程是可结合的(joinable),一个线程退出时,需要对其进行等待(pthread_join),否则无法释放资源,从而造成系统泄漏,倘若不关心线程的返回值、主线程不需要等待子线程,就可以将线程分离,退出时,自动释放线程资源。(等待thread线程的那个线程也是一种资源,不担心返回值时,就可以释放这个负担
2)可结合的
线程可以被其他线程收回其资源和杀死,在被其他的线程回收之前,它的存储资源(如栈)是不释放的。
3)分离的
线程不能被其他线程收回资源和杀死,它的存储资源(如栈)在它终止时由系统自动释放。
4)线程分离函数
①线程组内其他线程对目标线程进行分离

int pthread_detach(pthread_t thread);

②线程自己进行分离

pthread_detach(pthread_self());

猜你喜欢

转载自blog.csdn.net/guorong520/article/details/80228669