一.什么是线程
按照普遍的教科书上说的--线程是进程内部的一个执行流,是进程的一部分,比起进程它更加轻量化。通俗地说,在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序列”
既然这样,那进程里是不是能有很多个线程?是这样的,同时 一切进程至少都有一个执行线程 。
(改)线程在进程内部运行,本质是在进程地址空间内运行。在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程 执行流
二.如何描述线程
我们知道学习Linux中,一切的数据都需要管理,面对这么多的线程,操作系统肯定也要把这么多的线程管理起来,参考之前学习进程的经验--先描述再组织,对于线程也是如此。
那么如何描述线程?和进程类似,用一个PCB结构体描述。
在windows操作系统中是这样的,但是在Linux中采用了更巧妙的方法。
讲解进程的时候我们说过,进程 = 代码 + PCB + mm_struct + 页表 + mmu + 物理地址,如下:
Linux在创造线程时,并没有为线程再去单独设计结构,而是复用了进程的task_struck,具体如下:
由上图可以看到,Linux没有为线程单独设计PCB,而是复用进程的PCB,线程之间使用一个进程地址空间。对于CPU而言,此时的PCB是略小于真正的PCB的,但是CPU不管,它只需要执行就好了,这也是Linux这样设计的优点--即操作系统不需要额外设计数据结构管理线程,可以专注于线程间的资源调度。
以往我们学习是默认进程只有一个执行流,今天我们知道了进程是可以有多个执行流的。为什么要这么做呢?
因为进程在创建时需要消耗大量资源,资源包括两部分--时间和空间。
-
时间:cpu处理和创建PCB进程地址空间并等待资源的时间。
-
空间:创建的PCB和申请的进程地址空间。
三.初认识线程接口
说了这么多,我们先来使用下线程。
由于在Linux中线程是用进程模拟实现的,所以它不会为我们提供线程操作的接口,而是给给我们提供了一个在同一个进程地址空间中创建PCB的方法分配给资源指定的PCB。
对于我们来说,使用这样的方法创建线程代价太麻烦了,所以我们需要一个线程库,而一些应用开发工程师在应用层上对Linux接口封装成为了我们经常使用的原生线程库,接下来我们主要使用这个库来学习线程的各种函数。
1.线程创建
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
参数说明:
- 返回值:线程创建成功返回0,失败返回错误码。
- thread: 输出型参数,获取创建成功的线程ID。
- attr: 用于设置线程的属性,传入NULL表示默认
- start_routine: 函数地址,传入我们想要这个线程执行的函数
- arg: 传给线程启动函数的参数,需要强转为void*。
代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
void* run_thread(void* arg) //需要线程指向的函数
{
char* msg = (char*)arg;
while(1)
{
printf("I am the thread one\n");
sleep(1);
}
}
int main()
{
pthread_t tid;
pthread_create(&tid, NULL, run_thread, NULL);
//if(n != 0) printf("pthread create fail\n");
while(1)
{
printf("I am main thread\n");
sleep(1);
}
return 0;
}
我们发现想要编译程序时报错了,注意在执行线程有关的函数时,在编译时需要执行pthread库,修改后就可以执行成功了。
test : test.c
gcc -o $@ $^ -std=c11 -pthread
.PHONY:clean
clean:
rm -f test
Linux中可以用以下指令查看轻量级进程:
ps -aL
由上图可以看到,三个线程的PID是一样的,表明属于同一个进程,而它们的LWP不一样,说明对于操作系统来说,是通过LWP来分辨线程。
那之前学习进程时说的操作系统通过PID查看进程,错了吗?也没错,因为当时是在单执行流的情况下。
2.线程终止
既然能创建线程,那么肯定也能终止线程。如果只需要终止某个线程而不终止整个进程,有三个方法:
-
从线程函数中return,这种方法对主线程不适用,从main函数中return相当于调用exit。
-
线程可以通过pthread_exit终止自己。
-
一个线程可以调用pthread_cancel终止同一个进程中的另一个线程。
1)pthread_exit
void pthread_exit(void *value_ptr);
参数说明:
- 返回值:返回值为空,与进程同理,线程结束的时候无法返回到调用者,即自己本身(通俗地说,一旦线程调用pthread_exit退出自己,它就不存在了,也就无法返回)。
- value_ptr: 用来传递存储了线程退出信息的退出值,可以设置为NULL。
3.线程等待
int pthread_join(pthread_t thread, void **retval);
4.线程分离
int pthread_detach(pthread_t thread);
5.线程取消
#include <pthread.h>
/**
* 杀死(取消)线程.
* @param thread ⽬标线程ID.
* @return 成功: 0; 失败: 出错编号.
* 注意: 线程的取消并不是实时的,⽽⼜⼀定的延时。需要等待线程到达某个取消点(检查点)
*/
int pthread_cancel(pthread_t thread);