多线程1
目录
引入
-- 进程是系统中程序执行和资源分配的基本单位。每个进程有自己的数据段、代码段和堆栈段。由前面的学习 可知,实现进程间通信需要借助 Linux 操作系统中的专门通信机制,这些通信机制将占用大量的系统资源,线程间可以共享数据,更容易通信,因此引入了线程的概念。
-- 线程是在并发执行的多道执行路径,是一个更加接近于执行体的概念,拥有独立的执行序列,是进程的基本调度单元,每个进程至少都有一个main 线程。它与同进程中的其他线程共享进程空间{ 堆 代码 数据 文件描 述符 信号等},只拥有自己的栈空间,大大减少了资源的开销。
-- Linux 的线程是通过用户级的函数库实现的,一般采用 pthread 线程库实现线程的访问和控制。它用第三方posix 标准的 pthread,具有良好的可移植性。编译的时候要在后面加上 – lpthread。
-- 在一个进程中同一时刻只能运行一个while(1),而引入线程后就可以同时运行多个
认识线程
1、线程的概念
- 线程是一个轻量级的进程,多个线程运行在同一个进程空间里面。多个线程共享一个进程资源。
-- 进程有独立的地址空间,线程没有单独的地址空间。(同一进程内的线程共享进程的地址空间)
线程的特点类似于进程
1 异步性
2 并发性
3 动态性
4 独立性
进程能干的事情 线程都可以干
线程和进程可以达到同样的效果
-- 主函数 (main)就是一个线程
2、线程的优缺点
-
优点:占用系统的资源少,通信简单
-
缺点:调度没有进程方便,对资源的操作不安全
3、进程和线程的区别和联系
-- 区别
第一:和进程相比,它是一种非常“节俭” 的多任务操作方式。在 linux 系统中建立一个新的进程必须分配给它独立的地址空间, 建立众多的数据表来维护它的代码段、堆栈段和数据段等。
而运行于同一个进程的多个线程,它们之间使用相同的地址空间,而且线程间彼此切换所需的时间也远小于进程间切换的时间。
第二:线程间方便的通信机制。对于不同进程,它们有独立的数据空间 数据的交互只能使用进程通信的方式,这种方式费时,而且不方便。
但是同一个进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其他线程所有,快捷,方便。
-- 联系
线程和进程在使用上各有优缺点:线程执行开销小,占用的 CPU 资源少,线程之间的切换快,但不利于资源的管理和保护;
而进程正相反。从可移植性来讲,多进程的可移植性要好些。要注意的是由于线程共享了进程的资源和地址空间,因此,任何线程对系统资源的操作都会给其他线程带来影响,同时编写多线程程序,最复杂的事情是处理好各线程对共享资源的访问控制。
4、什么时候选进程,什么时候选线程?
-
1)需要频繁创建销毁的优先用线程,因为对进程来说创建和销毁一个进程代价是很大的。
-
2) 线程的切换速度快,所以在需要大量计算,切换频繁时用线程,还有耗时的操作使用线程可提高应用程序的响应
-
3)强相关的处理用线程,弱相关的处理用进程
(两件毫不相关的事情用进程,两个事件有很多要联系的用线程。) -
4)因为对CPU系统的效率使用上线程更占优,所以可能要发展到多机分布的用进程,多核分布用线程;
-
5)需要更稳定安全时,适合选择进程;需要速度时,选择线程更好。
线程相关函数
-- 在 linux 下线程操作使用的是别人的函数库来实现的 编译代码的时候需要添加 -lpthread
- gcc xxx.c -lpthread
1、创建线程
-- 函数头文件
- #include <pthread.h>
-- 函数原型
- int pthread_create(pthread_t * thread, const pthread_attr_t * attr, void * (* start_routine) (void *), void *arg);
-- 函数作用:
- 创建一个新线程
-- 函数参数
- thread:线程标识符,是一个指向线程标识符指针
线程号 ,定义一个pthread_t id; 填写&id即可。
- attr:创建出来线程的属性,一般用默认属性,NULL
- start_routine:线程运行函数的起始地址
void * (*start_routine) (void *):函数指针
创建线程出来之后 线程会去运行该函数
例如:void * func(void *a)
在该参数中填写 func 即可
- arg:传递给线程运行函数的参数
-- 返回值
- 成功:0
- 失败:-1
-- 线程共享全局区域,一个线程的非正常死亡会引起进程的死亡
2、线程的退出函数
-- 函数头文件
- #include <pthread.h>
-- 函数原型
- void pthread_exit(void *retval);
-- 函数作用:
- 退出当前线程
-- 函数参数
- retval:线程的返回值,通常传 NULL
-- 返回值
- 无
-- 该线程退出后,其他线程不会受到影响
3、阻塞等待线程退出 并回收资源
-- 函数头文件
- #include <pthread.h>
-- 函数原型
- int pthread_join(pthread_t thread, void **retval);
-- 函数作用:
- 阻塞等待指定的线程退出 并回收资源
-- 函数参数
- thread:线程标识符,要等待的线程号
- retval:用于获取线程退出的返回值
-- 返回值
- 成功:0
- 失败:-1
-- 获取返回值的办法过于繁琐,其实可以直接创建一个全局变量,把值赋值成8即可。
4、获取自身线程号的函数
-- 函数头文件
- #include <pthread.h>
-- 函数原型
- pthread_t pthread_self(void)
-- 函数的作用:
- 获取当前线程的线程号 因为有些函数需要线程号进行传递参数
-- 函数的返回值:
- 不会失败 返回线程的线程号
-- id的格式为%lu
5、主动取消一个线程
-- 函数的头文件
- #include <pthread.h>
-- 函数原型
- int pthread_cancel(pthread_t thread)
-- 函数的作用:
- 调用该函数可以主动的取消一个正在运行的线程
-- 函数的参数:
- thread:要取消的线程 id
-- 函数的返回值:
- 成功返回 0
- 失败返回 非零
6、注册线程退出清理函数
-- 函数头文件
- #include <pthread.h>
-- 函数原型
- void pthread_cleanup_push(void (*routine)(void *),void *arg);
- void pthread_cleanup_pop(int execute);
-- 函数的作用:
- 当想让线程退出时自动的执行某个函数时使用
-- 函数的参数:
- push 函数:void (*routine)(void *):函数指针 指向退出时要调用的函数
- void *arg:退出时调用的函数的传入的参数
- pop 函数:
execute为0,不执行退出清理函数,execute为1 执行退出清理函数
-- push 函数中注册的函数什么时候会被调用
- 1 当运行到 pop 函数时 该函数参数的参数不为 0 时
- 2 当未运行到 pop 函数时 遇到 pthread_exit 函数时
- 3 当未运行到 pop 函数时 被 pthread_cancel 函数取消时
-- !!!!!!!!!!! pop和push函数必须成对出现,否则会报错,
(1)push和pop必须定义在一个{}里
(2)push和pop之间的内容不能是死循环,要能到达pop函数
--
--
--
7、线程间通信
- 1 使用全局变量进行线程间的通信
- 2 发信号
#include <signal.h>
int pthread_kill(pthread_t thread, int sig)
-- 使用方法跟进程间的信号一模一样没有区别
-- 也可以使用信号改造函数