:线程
Posted 歌咏^0^
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了:线程相关的知识,希望对你有一定的参考价值。
目录
一.引入
并发:指进程的执行方式,指在同一时间内有多个进程同时运行
进程:程序关于某一数据集合的一次计算过程,是现代操作系统为了解决并发而特地引入的一个概念
系统分配资源是以进程为单位 ==》 进程地址空间、PCB进程控制块
进程 和 并发的出现大大提高了系统的执行效率
进程的执行效率能否在提高呢
以分时系统为例,每个进程依次执行一个 时间片 ==》 对CPU的持有时间是相等的
想提高进程的执行效率 ==》 提高进程对CPU的持有时间
===》 线程:线程是系统调度的最小单位
系统调度不是以进程为单位,而是以 线程为单位
二.线程是什么
进程 是程序关于某个数据集合的一次计算过程 ==》 执行态
线程 是进程的一个执行分支
如果一个进程只有一个执行分支,那么进程和线程等概念
===》 一个进程,可以拥有多个分支(多线程并发)
三.线程和进程的区别
1)
进程是操作系统分配资源的最小单位
线程是比进程更小的活动单位
系统调度的最小单位 ==》 进程拥有的线程数越多,对CPU的把持时间越长,处理效率越高
2)
进程 与 进程之间的地址空间是相互独立
同一进程的不同线程间共用同一块进程地址空间
3)
进程间通信比较麻烦,需要用到都能访问的第三方空间作为通信媒介
同一进程的不同线程间通信比较方便,只需要受保护(信号量samephore/互斥锁mutex)的全局变量就可以了
举个简单例子:
#include <stdio.h>
#include <stdlib.h>void* test(void* arg)
while(1)
printf("I'm test,heheda\\n");
sleep(1);
int main(void)
test(NULL);
while(1)
printf("I'm master,hahahahahaha\\n");
sleep(1);
程序输出结果为:
原因:
上述进程中只有一条执行分支,当该执行分支进入test函数时,遇上while(1)死循环出不来了
如果想两个死循环的printf都被执行,要怎么办
==> 需要两个执行分支
四.线程API
Linux中使用的线程是基于POSIX提供的线程机制 ==》 POSIX Thread 简称 pthread,由libpthread.so库提供相应接口函数(自带该库)
1)创建线程
NAME
pthread_create - create a new thread
创建一个新的线程(执行分支)
SYNOPSIS
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
@thread:指向一个pthread_t类型的变量空间,用来保存线程编号 tid
@attr:指定线程属性,一般为NULL,表示默认属性(如果需要修改属性,通常是后期用相应接口函数修改)
@start_routine:函数指针,指向一个 void*返回值,void*参数的函数,
即 指定新创建的线程要执行的函数,也被称为 线程函数
@arg:void*地址,表示传递给线程函数的参数
返回值:
成功返回 0
失败返回 !0,即失败的错误编码
Compile and link with -pthread. 编译链接时请加上 -pthread
练习:
请用线程,将上述例子代码中的两个printf数据都打印
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>void* test(void* arg)
while(1)
printf("I'm test,heheda\\n");
sleep(1);
int main(void)
pthread_t tid = 0;
int ret = 0;/* 创建一个新的线程,并指定线程去执行test函数 */
ret = pthread_create(&tid,NULL,test,NULL);
if(0 != ret)
printf("pthread_create error:%d\\n",ret);
return -1;
while(1)
printf("I'm master,hahahahahaha\\n");
sleep(1);
编译:
运行结果:
pstree命令以树状图查看进程信息
2)线程的退出
线程退出有两种方式:
S.主线程退出则表示进程退出,因此所有依赖于该进程的线程也全部会结束
a.线程主动退出
线程函数执行结束
在线程任意地方调用pthread_exit函数,来结束线程本身
NAME
pthread_exit - terminate calling threadSYNOPSIS
#include <pthread.h>
pthread_exit用来结束一个线程
void pthread_exit(void *retval);
@retval:return value,指定一个线程返回值(请不要指定局部变量地址 why? 最好是返回malloc的地址)
Compile and link with -pthread.
线程退出后,与进程退出相同,也会留下“尸体”,此时可以由其他线程调用 pthread_join函数来等待线程退出并回收资源
b.被其他线程请求退出
其他线程可以发送一个退出请求给指定的线程,让被请求的线程退出
NAME
pthread_cancel - send a cancellation request to a thread
发送一个取消请求给指定的线程
SYNOPSIS
#include <pthread.h>int pthread_cancel(pthread_t thread);
@thread:线程号
返回值:
成功返回 0
失败返回 !0,即失败的错误编码
Compile and link with -pthread.
eg:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>void* test(void* arg)
int a = *(int*)arg; //将传递进来的变量变为私有变量while(1)
printf("I'm test,heheda %d\\n",a);
sleep(1);
int main(void)
pthread_t tid = 0;
int ret = 0;
int a = 10086;/* 创建一个新的线程,并指定线程去执行test函数 */
ret = pthread_create(&tid,NULL,test,(void*)&a);
if(0 != ret)
printf("pthread_create error:%d\\n",ret);
return -1;
while(1)
a++;
if(a > 10090)
printf("%d 你这个王八蛋,我要干掉你\\n",tid);
pthread_cancel(tid);
printf("I'm master,hahahahahaha\\n");
sleep(1);
执行结果:
但是,被请求的线程是否退出,取决于该线程的一个属性 PHTEAD_CANCELED,创建的线程默认是可被取消的(如上所示)
可以通过函数 pthread_setcancelstate来设置这个属性
3)设置线程被取消属性
NAME
pthread_setcancelstate - set cancelability state
and typeSYNOPSIS
#include <pthread.h>
pthread_setcancelstate设置线程取消状态
int pthread_setcancelstate(int state, int *oldstate);
@state:指定要设置的取消状态
PTHREAD_CANCEL_ENABLE 可以被取消请求给取消
PTHREAD_CANCEL_DISABLE 禁止被取消
@oldstate:指向一个int空间用来保存原本的 装啊提
返回值:
成功 0
失败 -1
eg:
同上个例子,修改线程函数代码,增加设置不可取消属性后
void* test(void* arg)
int a = *(int*)arg; //将传递进来的变量变为私有变量
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
while(1)
printf("I'm test,heheda %d\\n",a);
sleep(1);
运行结果如下:
设置PTHREAD_CANCEL_DISABLE属性后的线程,就无法被外部线程给取消了!!!
4)等待线程退出
与进程退出相同,线程退出也可能留下尸体(未被回收的资源),此时需要其他线程调用pthread_join函数来负责回收
NAME
pthread_join - join with a terminated thread
等待一个线程退出,并回收其资源
SYNOPSIS
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
@thread:线程号tid,指定要等待哪个线程退出
@retvel:二级指针,一级指针的地址,用来保存线程的退出码(即 pthread_exit的参数)
Compile and link with -pthread.
但是也并非每个线程退出,都需要其他线程来回收资源,这取决于线程的“分离属性”是否被设置
如果设置了分离属性,则线程退出时会自动回收资源
如果没设置分离属性,则必须要有人回收
5)设置线程分离属性
NAME
pthread_detach - detach a thread
用来分离一个线程,使其在退出后,自动回收资源
SYNOPSIS
#include <pthread.h>
int pthread_detach(pthread_t thread);
@thread:线程号,指定要设置分离属性的线程
Compile and link with -pthread.
设置了detach属性的线程,是不会使pthread_join函数进入阻塞
6)线程的互斥问题
对于同一进程的不同线程来说,他们都是公用同一个进程地址空间,因此对这些线程来说第三方资源就太多了
硬件、内核、系统、全局变量,因此也就存在线程就存在线程间数据传输的保护方式
===》等同于进程间通信所存在的问题:多个线程操作同一个共享资源时,资源状态存在不确定性
特别对于那些不允许被多个进程/线程同时访问的内容 ==》 互斥资源
在线程中,能起到“保护”作用的有两套机制
a)信号量(同步互斥)
system_V 信号量
POSIX 信号量
b)线程互斥锁(互斥)
用于同一进程的不同线程间,保护互斥资源的一种手段
在POSIX线程机制中,用pthread_mutex_t 类型(全局变量)来描述一个互斥锁
互斥锁的实现类似于信号量
==注意==:需要额外安装man手册对POSIX的支持
sudo apt-get install manpages-posix-dev
sudo apt-get install manpages-posix
1、初始化线程互斥锁
NAME
pthread_mutex_init —initialize a mutex
初始化一个线程互斥锁
SYNOPSIS
#include <pthread.h>int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
@mutex:pthread_mutext_t类型的指针(变量取地址),指定要初始化的线程互斥锁
@attr:指定线程互斥锁的属性,一般为NULL,表示默认属性
返回值:
成功为0
失败为!0,即错误编码
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
eg:
定义并初始化一把线程互斥锁
pthread_mutex_t mutex; //全局变量,互斥锁
ret = pthread_mutex_init(&mutex,NULL);
if(0 != ret)
printf("mutex inint error\\n");
return -1;
2、P操作/上锁
NAME
pthread_mutex_lock, pthread_mutex_trylock, pthread_mutex_unlock —
lock and unlock a mutexSYNOPSIS
#include <pthread.h>
上锁,能锁就锁,不能锁就一直等
int pthread_mutex_lock(pthread_mutex_t *mutex);
尝试上锁,能锁就锁,不能锁就返回失败
int pthread_mutex_trylock(pthread_mutex_t *mutex);
==#临界区==
3、V操作/解锁
解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
4、销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
在线程中,线程互斥锁只能用于互斥,无法同步,那线程中如何实现多线程同步执行呢
注意:
线程结束时,一定记住放锁,切勿带锁死亡
7)同步问题
线程的同步问题被称为 条件变量,在POSIX的线程机制中用 pthread_cond_t 来描述一个条件变量
条件变量的概念:
a) 在多线程程序设计中,我们用条件变量表示某一选定的条件
比如:
主线程要等分子线程循环10次后,再开始执行
b)条件变量上的操作:初始化、等待、唤醒
大致实现机制如下:
(伪代码)
void* thread_fun1(void* arg)
wait; //等待某个条件变量
void* thread_fun2(void* arg)
……
if(xx条件)
signal;//唤醒指定条件变量
条件变量用于多线程同步,因此条件变量本身是一个 共享资源 ====》 需要互斥锁保护
修改后的伪代码如下
void* thread_fun1(void* arg)
lock;
wait; //解锁 并 等待某个条件变量(线程条件变量等待的本身会具备解锁功能)
unlock();
void* thread_fun2(void* arg)
……
if(xx条件)
lock;
signal;//唤醒指定条件变量
unlock;
条件变量API
1.初始化条件变量
NAME
pthread_cond_destroy, pthread_cond_init — destroy and initialize
condition variablesSYNOPSIS
#include <pthread.h>pthread_cond_init用来初始化一个条件变量
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
@cond:指定要初始化的条件变量
@attr:指定条件变量属性,一般为NULL
eg:
pthread_cond_t cond;
pthread_cond_init(&cond,NULL);
2.等待条件变量
NAME
pthread_cond_timedwait, pthread_cond_wait — wait on a conditionSYNOPSIS
#include <pthread.h>
pthread_cond_wait等待一个条件变量(死等)
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
@cond:指定要等待的条件变量
@mutex:指定用来保护条件变量的那个互斥锁
pthread_cond_timedwait 超时等待,具体用法,结合cond和POSIX信号量一起看,你就懂了,而且懂了两个知识
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);
==线程的条件变量在进入等待之前,会先将保护自己的互斥锁给解锁,一遍其他线程来访问条件变量并唤醒正在等待的线程==
3.唤醒条件变量
NAME
pthread_cond_broadcast, pthread_cond_signal — broadcast or signal a
conditionSYNOPSIS
#include <pthread.h>
pthread_cond_broadcast广播,唤醒条件变量上所有进入等待的线程
int pthread_cond_broadcast(pthread_cond_t *cond);pthread_cond_signal 唤醒条件变量上一个正在等待的线程
int pthread_cond_signal(pthread_cond_t *cond);
4.销毁条件变量
pthread_cond_destroy 用来销毁指定的条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
以上是关于:线程的主要内容,如果未能解决你的问题,请参考以下文章