1.线程概念
指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理或同时多线程处理器。在一个程序中,这些独立运行的程序片段叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理”
使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。
使用多线程的理由之二是线程间方便的通信机制
。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一
进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用
,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static
的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。
1.1什么是线程
LWP: light weight process
轻量级的进程,本质仍是进程
(
在
Linux
环境下
)
。
进程 : 有独立 PCB ,且有独立地址空间,线程 : 有独立 PCB ,但没有独立的地址空间 ( 共享 ) 。区别: 在于是否共享地址空间。独居( 进程 ): 合租 ( 线程 )
Linux
下:
线程: 最小的执行单位。进程: 最小分配资源单位(可看成是只有一个线程的进程)
进程和线程的区别:
- 地址空间:
线程共享本进程的地址空间,而进程之间是独立的地址空间。
- 资源:
线程共享本进程的资源如内存、
I/O
、
cpu
等,不利于资源的管理和保护,而进程之间的资源是独立的,
能很好的进行资源管理和保护。
- 健壮性:
多进程要比多线程健壮,一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。
- 执行过程:
每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口,执行开销大。
但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,执行开销小。
- 可并发性:
两者均可并发执行。
- 切换时:
进程切换时,消耗的资源大,效率低。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程。
- 其他:
线程是处理器调度的基本单位,但是进程不是
命令: ps -Lf 进程id --- >LWP 线程号
1.2Linux内核实现线程原理
类
Unix
系统中,借助进程机制实现出了线程的概念。因此在这类系统中,进程和线程关系密切。
1. 轻量级进程 (ight-weight process), 也有 PCB ,创建线程使用的底层函数和进程一样, 都是 clone2. 从内核里看进程和线程是一样的, 都有各自不同的 PCB ,但是线程 PCB 中指向内存资源的三级页表是相同的。3. 进程可以蜕变成线程。4. 线程可看做寄存器和栈的集合。5. 在 linux 下,线程最是小的执行单位 : 进程是最小的分配资源单位。
三级映射
进程
相同的地址 ( 同一个虚拟地址 ) 在不同的进程中,反复使用而不冲突 . 原因是他们虽然虚拟地址一样,但页目录、页表、物理页面各不相同。相同的虚拟地址,映射到不同的物理页面内存单元,最終访问不同的物理页面。
线程
两个线程具有各自独立的 PCB ,但共享 == 同一个页目录 == ,也就共享同一个页表和物理页面。所以两个 PCB 共享一个地址空间。
实际上,无论是创建进程的
fork,
还是创建线程的
pthread_create,
底层实现都是调用同一个内核函数
clone
如果复制对方的地址空间,那么就产生一个
“
进程
”
;如果共享对方的地址空间,就产生一个
“
线程
”
。
因此
: Linux
内核是不区分进程和线程的。只在用户层面上进行区分。所以,线程所有操作函数是
pthread
是库函数,而非系统调用。
1.3线程共享资源
1. 文件描述符表2. 每种信号的处理方式3. 当前工作目录4. 用户 ID 和组 ID5. 内存地址空间 (.text/.data/.bss/heap/ 共享库 ) 、共享全局变量
1.4线程非共享资源
1. 线程 id (线程标识符)2. 处理器现场和栈指针 ( 内核栈 )3. 独立的栈空间 ( 用户空间栈 )4.errno 变量5. 信号屏蔽字6. 调度优先级
1.5线程优、缺点
优点
:
1.提高程序并发性2.开销小3.数据通信、共享数据方便。
缺点
:
1.属于库函数,不稳定2.调试、编写困难、 gdb 不支持3.对信号支持不好。
命令安装: sudo apt install manpages-posix-dev
2.线程控制原语
2.1 pthread_self函数
获取当前程序的线程ID。其作用对应进程中getpid())函数
pthread_t pthread_self ( void );返回值 :成功 : 调用的线程 ID失败 : 无 !
线程
ID
:
pthread_t
类型,本质:在
Linux
下为无符号整数
(%lu),
其他系统中可能是结构体实现。
线程ID是进程内部,识别标志。
(
两个进程间,线程
ID
允许相同
)
。
注意 :不应使用全局变量pthread_t tid,在程序中通过pthread_create 传出的参数是新创建的线程ID,而应使用pthread_self。
例程:获取线程
id
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <pthread.h>
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main()
{
pthread_t tid;
tid=pthread_self();
printf("main() pid=%d ,tid=%lu\n",getpid(),tid);
while(1);
return 0;
}
2.2pthread_create函数
创建一个新线程。其作用,对应进程中
fork()
函数。
int pthread_create ( pthread_t * thread , const pthread_attr_t * attr ,void * ( * start routine ) ( void * ), void * arg );返回值 :成功 : 0 ;失败 : 错误号 ------- Linux 环境下,所有线程特点,失败均直接返回错误号参数 :pthread_t : 当前 Linux 中可理解为 : typedef unsigned long int pthread_t ;参数 1 : 传出参数,保存系统为我们分配好的新线程 ID 。参数 2 : 线程属性,通常传 NULL , 表示使用线程默认属性。若想使用具体属性也可以修改该参数。参数 3 :回调子线程函数名参数 4 :参 3 子线程函数需要的参数,没的话传 NULL
编译命令增加参数 -lpthread
例程:创建子线程,各自循环打印信息
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <pthread.h>
void sys_err(const char *str)
{
perror(str);
exit(1);
}
void *tfn(void *arg)
{
printf("thread: pid=%d, tid=%lu\n",getpid(),pthread_self());
int i=0;
while(i<10)
{
printf("thread----tfn()--do count=%d\n",i);
sleep(1);
i++;
}
//while(1);
return NULL;
}
int main()
{
pthread_t tid1;
pthread_t tid2;
tid1=pthread_self();
printf("main() pid=%d ,tid=%lu\n",getpid(),tid1);
//ptread_create()创建线程
//参1:传出 一个变量,这个变量用于保存新创建的线程id
//参2:传入 *attr所指向的内容不能修改,或是传入常量
//参3:传入 回调函数地址,函数名就是地址,所以传函数名
//参4:传入 回调函数需要的参数
//返回值:0代表成功,失败返回错误码
int ret=pthread_create(&tid2,NULL,tfn,NULL);
if(ret!=0)
{
sys_err("pthread_create err");
}
printf("------main() thread:
pid=%d,tid2=%d,tid=%lu\n",getpid(),tid2,pthread_self());
int i=0;
while(i<15)
{
printf("thread----main()----count=%d\n",i);
sleep(1);
i++;
}
return 0;
}
例程:创建
5
个子线程,每个线程独立打印自己的线程
id
#include<stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <pthread.h>
void sys_err(const char *str)
{
perror(str);
exit(1);
}
void *tfn(void *arg)
{
int i=(long)arg;
printf("--I am %dth thread pid=%d, tid=%lu\n",i+1,getpid(),pthread_self());
// sleep(i);
}
int main()
{
int i;
int ret;
pthread_t tid;
for(i=0;i<5;i++)
{
ret=pthread_create(&tid,NULL,tfn,(void *)(long)i);
if(ret!=0)
{
sys_err("pthread_create() err");
}
sleep(i);
}
sleep(i);
printf("pthread_create finish!\n");
return 0;
}