Linux 系统编程 学习:09-线程:线程的创建、回收与取消
Linux 系统编程 学习:09-线程:线程的创建、回收与取消
背景
我们在此之前完成了 有关进程的学习。从这一讲开始我们学习线程。
完全的开发可以参考:《多线程编程指南》
在Linux 系统编程 学习:有关概念中,我们介绍了线程和进程的概念。
概念
基础概念:
- 线程是cpu或操作系统调度的基本单位。线程大部分的资源是共享的,仅仅申请了自己的栈、空间。
- 线程是进程内部的一个执行分支,线程量级很小。
- 在程序中创建线程,可以提高效率,进程内线程越多,争夺到CPU的概率就越大,执行代码的概率就越大(有一个度)。
- 线程可以解决很多问题,而不会像进程一样有那么多的开销。
- 在线程中需要注意同步的问题。一个线程的bug很可能会引起该进程的崩溃。
线程与进程的内存分布不同:
- 每个进程在创建的时候都申请了新的内存空间以存储代码段\数据段\BSS段\堆\栈空间,并且这些的空间的初始化值是父进程空间的,父子进程在创建后不能互访资源。
- 而每个新创建的线程则仅仅申请了自己的栈、空间;与同进程的其他线程共享该进程的其他数据空间包括代码段\数据段\BSS段\堆以及打开的库、mmap映射的文件与共享的空间,使得同进程下的线程共享数据十分的方便,只需借助这些共享区域即可,但也有问题即是同步问题。
线程开发基本步骤
在接下来的开发中,我们会介绍有关的函数;
所有线程是在
<pthread.h>
中,且编译时需要链接pthread
库;下面不再说明。
同时,如果没有特殊说明,所有的函数在失败时都返回错误号(error number)。
线程的创建、回收与取消
创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
描述:创建一个线程,线程创建成功以后,开始执行指定的函数。
默认情况下,一个线程所使用的内存资源在应用pthread_join调用之前不会被重新分配,所以对于每个线程必须调用一次pthread_join函数(分离线程除外)。
参数解析:
thread:存放线程ID号的对象
attr:创建线程时设置的有关属性,可为空(这里先略过,我们会在后面专门讲到)
start_routine:线程执行的函数入口
arg:执行函数时附带的参数,可为空
返回值:成功返回0,从thread可以获取到线程ID;失败返回错误号。
回收线程
int pthread_join(pthread_t thread, void **retval);
描述:阻塞等待一个线程,并回收其资源。
默认情况下,新创建的线程是joinable的,线程退出后,需对其进行pthread_join操作。
如果不关心线程的返回值,我们可以告诉系统,当线程退出时,自动释放线程资源(后面我们会讲到)
参数解析:
thread:指定等待的线程号
retval:非空时,获取由pthread_exit(void *retval);
函数传过来的结果。
返回值:成功返回0,失败返回error number。
提出一个终止线程的请求
int pthread_cancel(pthread_t thread);
描述:一般情况下,线程在其主体函数退出的时候会自动终止,但同时也可以因为接收到另一个线程发来的终止(取消)请求而强制终止。
同一进程的线程间,pthread_cancel向另一线程发终止信号。如何处理Cancel信号则由目标线程自己决定:忽略、立即终止、继续运行至Cancelation-point(取消点),由不同的Cancelation状态(pthread_setcancelstate函数设置状态)决定。
被取消线程可以调用pthread_testcancel,让内核去检测是否需要取消当前线程。被取消的线程退出时总是返回
-1
(常数值PTHREAD_CANCELED)。如果在取消功能处处于禁用状态下调用pthread_testcancel(),则该函数不起作用。 请务必仅在线程取消线程操作安全的序列中插入pthread_testcancel()。除通过pthread_testcancel()调用以编程方式建立的取消点意外,pthread标准还指定了几个取消点。测试退出点,就是测试cancel信号
返回值: 发送成功返回0(不意味着thread会终止);失败返回错误号。
一个取消线程的例程
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
void *thread_fun(void *arg)
{
int i=1;
printf("thread start \n");
while(1)
{
// pthread_testcancel(); 如果没有这句话,那么线程不会结束。
i++;
}
return (void *)0;
}
int main(int argc, char* argv)
{
void *ret = NULL;
int iret = 0;
pthread_t tid;
pthread_create(&tid, NULL, thread_fun, NULL);
sleep(1);
pthread_cancel(tid);//取消线程
pthread_join(tid, &ret);
printf("thread 3 exit code %d\n", (int)ret);
return 0;
}
线程取消
线程在收到取消请求(pthread_cancel)后会继续运行,直到到达某个取消点(CancellationPoint)。
取消点:线程检查是否被取消并按照请求进行动作的一个位置。
pthreads标准指定了几个取消点,其中包括:
(1)通过pthread_testcancel
调用以编程方式建立线程取消点。
(2)线程等待pthread_cond_wait
或pthread_cond_timewait()
中的特定条件。 (错误的程序设计可能会在取消时导致死锁)
(3)被sigwait()
阻塞的函数
(4)一些标准的库调用。通常,这些调用包括线程可基于阻塞的函数。
设置一个线程能否被取消
int pthread_setcancelstate(int state, int *oldstate)
描述:设置一个线程能否被取消
参数解析:
state: 状态
- PTHREAD_CANCEL_ENABLE(缺省动作,收到信号后设为CANCLED状态)
- PTHREAD_CANCEL_DISABLE(忽略CANCEL信号继续运行)
old_state: 旧状态容器,如果不为 NULL则存入原来的Cancel状态。
设置本线程取消动作的执行时机
int pthread_setcanceltype(int type, int *oldtype)
描述:设置本线程取消动作的执行时机(仅当Cancel状态为Enable时有效)
参数解析:
type : 取消类型
- PTHREAD_CANCEL_DEFFERED (默认,收到信号后继续运行至下一个取消点再退出)
- PTHREAD_CANCEL_ASYCHRONOUS, (立即执行取消动作——退出)
oldtype : 旧状态容器,如果不为NULL则存入运来的取消动作类型值。
手动创建取消点
void pthread_testcancel(void)
描述: 手动创建一个取消点,但线程设置了PTHREAD_CANCEL_ENABLE
与PTHREAD_CANCEL_DEFFERED
属性,且已经有线程发送了取消本线程的请求时,退出;否则直接返回。
注意:由于此函数在线程内执行,执行的位置就是线程退出的位置,所以在执行此函数以前,线程内部的相关资源申请一定要释放掉,很容易造成内存泄露
总结:
1)线程可以调用pthread_setcancelstate()
设置被取消的,或者不能被取消的
2)线程的取消的本质是处理取消信号
3)取消线程可以马上进行的,也可以在取消点才取消 (pthread_setcanceltype()
)
若是在整个程序退出时,要终止各个线程,应该在成功发送 CANCEL 指令后,使用 pthread_join 函数, 等待指定的线程已经完全退出以后, 再继续执行; 否则,很容易产生 “段错误”。
线程遗嘱
遗嘱机制一般用于释放一些资源,比如释放锁,以免其它的线程永远 也不能获得锁,而造成死锁。
void pthread_cleanup_push(void (*routine)(void *), void *arg);
void pthread_cleanup_pop(int execute);
登记执行压栈清理函数
void pthread_cleanup_push(void (*routine)(void *), void *arg);
描述:登记执行压栈清理函数的操作。
参数解析:
routine:清理函数
arg:清理函数的有关参数
当以下描述的情况发生时自动调用的函数:
- 线程调用
pthread_exit()
函数,而不是直接return. - 响应取消请求时,也就是有其它的线程对该线程调用
pthread_cancel()
函数而到达取消条件时。 - 本线程调用pthread_cleanup_pop()函数,并且其参数非0。
从栈中删除清理函数的操作
void pthread_cleanup_pop(int execute);
描述:从栈中删除清理函数的操作。
参数解析:
execute:标志位,当其非0时,执行pthread_cleanup_push
中登记好的函数;否则,将其出栈,不执行。
例程
2条线程使用互斥锁(后面会讲到)抢占一个资源,当占有锁的其中一个线程被意外中断时之前写好了释放锁的遗嘱,另外一条能够正常拿到锁。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
pthread_mutex_t mutex_x=PTHREAD_MUTEX_INITIALIZER;
void clean_handler(void *arg)
{
pthread_mutex_unlock((pthread_mutex_t *)arg); // 防止死锁,所以在这里添加解锁操作
printf("unlocked from clean_handler push by thread_fun1\n");
}
void *thread_fun1(void *arg)
{
int i=1;
printf("thread1 start \n");
pthread_cleanup_push(clean_handler, &mutex_x);//提前登记线程被取消后需要处理的事情
pthread_mutex_lock(&mutex_x);
printf("thread_fun1 locked.\n");
while(1)
{
pthread_testcancel(); //如果没有这句话,那么线程不会结束。
i++;
}
pthread_mutex_unlock(&mutex_x);
printf("thread_fun1 unlocked\n");
pthread_cleanup_pop(0);
return (void *)0;
}
void *thread_fun2(void *arg)
{
int i=1;
printf("thread2 start \n");
for (i = 0; i < 6; ++i)
{
pthread_mutex_lock(&mutex_x);
printf("thread_fun2 locked.\n");
sleep(1);
pthread_mutex_unlock(&mutex_x);
printf("thread_fun2 unlocked\n");
}
printf("thread2 end \n");
return (void *)0;
}
int main(int argc, char* argv)
{
void *ret = NULL;
int iret = 0;
pthread_t tid1;
pthread_t tid2;
pthread_create(&tid1, NULL, thread_fun1, NULL);
pthread_create(&tid2, NULL, thread_fun2, NULL);
sleep(2);
pthread_cancel(tid1);//取消线程
pthread_join(tid1, &ret);
iret =(int)( (int*)ret);
printf("thread 3 exit code %d\n", iret);
while(1);
return 0;
}
Linux 系统编程 学习:09-线程:线程的创建、回收与取消的更多相关文章
- Linux 系统编程 学习:10-线程:线程的属性
Linux 系统编程 学习:10-线程:线程的属性 背景 上一讲我们介绍了线程的创建,回收与销毁:简单地提到了线程属性.这一讲我们就来具体看看,线程的属性. 概述 #include <pthre ...
- Linux 系统编程 学习:11-线程:线程同步
Linux 系统编程 学习:11-线程:线程同步 背景 上一讲 我们介绍了线程的属性 有关设置.这一讲我们来看线程之间是如何同步的. 额外安装有关的man手册: sudo apt-get instal ...
- Linux 系统编程 学习 总结
背景 整理了Liunx 关于 进程间通信的 很常见的知识. 目录 与 说明 Linux 系统编程 学习:000-有关概念 介绍了有关的基础概念,为以后的学习打下基础. Linux 系统编程 学习:00 ...
- Linux 系统编程 学习:00-有关概念
Linux 系统编程 学习:00-有关概念 背景 系统编程其实就是利用系统中被支持的调度API进行开发的一个过程. 从这一讲开始,我们来介绍有关Linux 系统编程的学习. 知识 在进行Linux系统 ...
- Linux 系统编程 学习:01-进程的有关概念 与 创建、回收
Linux 系统编程 学习:01-进程的有关概念 与 创建.回收 背景 上一讲介绍了有关系统编程的概念.这一讲,我们针对 进程 开展学习. 概念 进程的身份证(PID) 每一个进程都有一个唯一的身份证 ...
- Linux 系统编程 学习:02-进程间通信1:Unix IPC(1)管道
Linux 系统编程 学习:02-进程间通信1:Unix IPC(1)管道 背景 上一讲我们介绍了创建子进程的方式.我们都知道,创建子进程是为了与父进程协作(或者是为了执行新的程序,参考 Linux ...
- Linux 系统编程 学习:03-进程间通信1:Unix IPC(2)信号
Linux 系统编程 学习:03-进程间通信1:Unix IPC(2)信号 背景 上一讲我们介绍了Unix IPC中的2种管道. 回顾一下上一讲的介绍,IPC的方式通常有: Unix IPC包括:管道 ...
- Linux 系统编程 学习:04-进程间通信2:System V IPC(1)
Linux 系统编程 学习:04-进程间通信2:System V IPC(1) 背景 上一讲 进程间通信:Unix IPC-信号中,我们介绍了Unix IPC中有关信号的概念,以及如何使用. IPC的 ...
- Linux 系统编程 学习:05-进程间通信2:System V IPC(2)
Linux 系统编程 学习:05-进程间通信2:System V IPC(2) 背景 上一讲 进程间通信:System V IPC(1)中,我们介绍了System IPC中有关消息队列.共享内存的概念 ...
随机推荐
- @DependsOn注解的使用
如果Bean A 在创建前需要先创建BeanB此时就可以使用DependsOn注解 @Configuration public class MyConfig { @Bean @DependsOn(&q ...
- spring源码之bean的初始化及循环引用
实例化方法,把bean实例化,并且包装成BeanWrapper 1.点进这个方法里面. 这个方法是反射调用类中的 factoryMethod 方法. 这要知道@Bean 方法的原理, 实际上sprin ...
- mysql-11-DML
#DML语言 /* 数据操作语言 插入:insert 修改:update 删除:delete */ #一.插入语句 /* 语法: insert into 表名(列名...) values(新值...) ...
- sqli-labs第三关 详解
通过第二关,来到第三关 我们用了前两种方法,都报错,然后自己也不太会别的注入,然后莫名的小知识又增加了.这居然是一个带括号的字符型注入, 这里我们需要闭合前面的括号. $sql=select * fr ...
- 【暂咕咕咕】SuffixTree
#include<bits/stdc++.h> using namespace std; const int MAXN=1e6+10; typedef long long ll; char ...
- Jmeter5.3源码编译
下载源码 https://jmeter.apache.org/download_jmeter.cgi 配置网络环境(重要) 下载 Proxifier 配置上网条件 导入Idea 通过 Idea 的 O ...
- SQL审核平台Yearning部署
SQL审核平台Yearning部署 Yearning优势: Yearning SQL 审计平台 基于Vue.js与Django的整套mysql-sql审核平台解决方案.提供基于Inception的S ...
- Oracle报错>记录被另外一个用户锁定
原因 当一个用户对数据进行修改时,若没有进行提交或者回滚,Oracle不允许其他用户修改该条数据,在这种情况下修改,就会出现:"记录被另外一个用户锁定"错误. 解决 查询用户.数据 ...
- 霍夫曼编码(Huffman)
题目:有一个字符串:cabcedeacacdeddaaaba,问题: (1)采用霍夫曼编码画出编码的过程,并写出各字符的编码 (2)根据求得的编码,求得各编码需要的总位数 (3)求出整个字符串总编码长 ...
- 数据结构&算法的引言&时间复杂度
什么是计算机科学? 首先明确的一点就是计算机科学不仅仅是对计算机的研究,虽然计算机在科学发展的过程中发挥了重大的作用,但是它只是一个工具,一个没有灵魂的工具而已.所谓的计算机科学实际上是对问题.解决问 ...