2019-08-07 16:49:49 cbnzww 阅读数 48
  • Linux零基础视频课程

    Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和UNIX的多用户、多任务、支持多线程和多CPU的操作系统。针对的是Linux服务器方面的知识,适合从事运维或后端开发的人员。 课程难点:     Linux命令较多,没有图形界面,对于新手理解有难点,复杂的命令也容易记混淆。

    3594 人正在学习 去看看 李强

一、线程基本概念

  1. 线程就是进程中的执行路线,即进程内部的控制序列,或者说是进程的子任务(进程就是正在运行的程序,它是一个资源单位)
  2. 线程是轻量级的,没有自己独立的内存资源,使用的是进程的代码段,数据段,bss段,堆(注意没有栈),环境变量表、命令行参数、文件描述符、信号处理函数、工作目录、用户ID、组ID等资源
  3. 线程拥有自己独立的栈,也就是有自己独立的局部变量
  4. 一个进程中可以同时拥有多个线程,即同时被系统调度的多条执行路线,但至少有一个主线程

二、线程基本特点

  1. 线程是进程的实体,可作为系统独立的调试和分派基本单位
  2. 线程有不同的状态,系统提供了多种线程控制的原语(控制方法)
  3. 线程不拥有自己的资源(唯一拥有的就是自己的栈空间),只拥有从属于进程的全部资源,所有有资源分配都是面向进程的
  4. 一个进程中可以有多个线程同时执行,他们可以执行相同的代码,也可以执行不同的代码
  5. 同一进程内的线程都在同一地址空间下活动(0~4G),相对于多进程,多线程的系统开销小,任务切换快
  6. 多进程协同工作时需要通信,而多线程间的数据交换不需要依赖类似IPC的特殊通信机制,简单而高效
  7. 每个线程拥有自己独立的线程ID、寄存器信息、函数栈等
  8. 线程之间也存在优先级

三、POSIX线程

  1. 早期的UNIX操作系统是没有线程的,而各个计算机厂商提供自己私有的线程库,不易于移植
  2. 在1995年左右,定义了统一的线程编程接口。POSIX线程,即pthread
  3. pthread包含一个头文件pthread.h,一个共享库libpthread.so
  4. 功能:
    • 线程管理:创建/销毁、分离/联合、设置/获取属性
    • 线程同步(互斥):互斥量(互斥锁)、条件变量、信号量

四、线程函数

头文件#include <pthread.h>


创建线程

函数声明int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
功能:创建线程
thread:获取线程ID
attr:创建线程时所需要的属性设置,如果为NULL按照默认方式创建线程
start_routine:线程的入口函数
arg:给线程入口函数传递的参数

练习1:把tcp的S端的多进程改成多线程


等待线程结束

函数声明int pthread_join(pthread_t *thread, void **retval);
功能:等待线程结束获取线程入口函数的返回值,线程结束时该函数才返回
thread:获取线程ID
retval:指针变量的地址,用于获取线程入口函数的返回值
注意:线程入口函数在返回数据时,不能返回指针私有栈空间爱你的指针,如果获取到的是指向堆的指针


获取当前线程ID

函数声明int pthread_self(void);


比较线程ID

函数声明int pthread_equal(pthread_t t1, pthread_t t2);
功能:比较线程ID:如果两个线程ID是用一个线程,则返回0,否则返回-1
注意:pthread_t不一定是unsigned long 类型,有些系统中它是结构体类型,所以无法使用 == 进行比较


线程终止

函数声明void pthread_exit(void **retval);
功能:调用者线程结束(从入口函数return)
retval:会返回给pthread_join函数的第二个参数
注意:如果是进程的最后一个线程,当调用pthread_exit时进程也结束


线程分离

  • 分离态:线程可以被创造者调用pthread_join等待(回收资源)
  • 非分离态:线程不需要创造者等待,结束后自动释放资源

函数声明int pthread_detach(pthread_t thread);
功能:使调用线程与线程ID为thread线程成为分离状态


线程取消

头文件#include <pthread.h>
函数声明int pthread_cancel(pthread_t thread);
功能:向指定的线程发送取消操作
注意:对方不一定响应,响应则取消


设置手否响应取消线程操作

函数声明:int pthread_setcancelstate(int state, int *oldstate);
state

  • PTHREAD_CANCEL_ENABLE : 允许响应
  • PTHREAD_CANCEL_DISABLE:禁止相应
    oldstate:获取旧的取消状态

线程属性

 typedef union
	{
		char __size[__SIZEOF_PTHREAD_ATTR_T];
		long int __align;
	}pthread_attr_t;
	猜测:不让手动修改线程的各大项属性,而使用pthread_attr_set/get系列函数来操作。

功能:初始化线程属性

int pthread_attr_init(pthread_attr_t *attr);

功能:销毁线程属性

int pthread_attr_destroy(pthread_attr_t *attr);


功能:设置线程属性中分离标志

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
detachstate:

  • PTHREAD_CREATE_DETACHED :分离
  • PTHREAD_CREATE_JOINABLE :不分离

功能:获取线程属性中分离标志

int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);


功能:设置线程属性中线程的竞争范围

int pthread_attr_setscope(pthread_attr_t *attr, int scope);

  • PTHREAD_SCOPE_SYSTEM :系统中
  • PTHREAD_SCOPE_PROCESS:过程中

功能:获取线程属性中线程的竞争范围

int pthread_attr_getscope(pthread_attr_t *attr, int *scope);


功能:设置线程属性中线程的调度策略的来源

int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);
inheritsched

  • PTHREAD_INHERIT_SCHED 继承创建者
  • PTHREAD_EXPLICIT_SCHED 单独设置

功能:获取线程属性中线程的调度策略来源

int pthread_attr_getinheritsched(pthread_attr_t *attr,int *inheritsched);


功能:设置线程属性中线程的调度策略

int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);

  • SCHED_FIFO 先进先出策略
  • SCHED_RR 轮转
  • SCHED_OTHER 缺省

功能:获取线程属性中线程的调度策略

int pthread_attr_getschedpolicy(pthread_attr_t *attr, int *policy);


功能:设置线程属性中线程的调度参数(优先级别)

int pthread_attr_setschedparam(pthread_attr_t *attr,const struct sched_param *param);
param: 最高级别 0 :就绪状态

功能:获取线程属性中线程的调度参数(优先级别)

int pthread_attr_getschedparam(pthread_attr_t *attr,struct sched_param *param);


功能:设置线程属性中栈尾的警戒区大小,默认一页

int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);

功能:获取线程属性中栈尾的警戒区大小

int pthread_attr_getguardsize(pthread_attr_t *attr, size_t *guardsize);


功能:设置线程属性中线程的栈底地址

int pthread_attr_setstackaddr(pthread_attr_t *attr, void *stackaddr);

功能:获取线程属性中线程的栈底地址

int pthread_attr_getstackaddr(pthread_attr_t *attr, void **stackaddr);


功能:设置线程属性中线程的栈空间字节数

int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);

功能:获取线程属性中线程的栈空间字节数

int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);


功能:设置线程属性中线程的栈底地址和栈空间字节数

int pthread_attr_setstack(pthread_attr_t *attr,void *stackaddr, size_t stacksize);

功能:获取线程属性中线程的栈底地址和栈空间字节数

int pthread_attr_getstack(pthread_attr_t *attr,void **stackaddr, size_t *stacksize);


功能:获取制定线程的属性

int pthread_getattr_np(pthread_t thread, pthread_attr_t *attr);


使用方法

  1. 定义线程属性结构体
  2. 初始化线程属性结构体
  3. 使用pthread_attr_set系列函数对结构体变量进行设置
  4. 在创建线程时(pthread_create函数的第二个参数)使用线程属性结构体变量创建线程

作业:实现聊天室

2018-09-01 08:32:37 weixin_40797414 阅读数 308
  • Linux零基础视频课程

    Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和UNIX的多用户、多任务、支持多线程和多CPU的操作系统。针对的是Linux服务器方面的知识,适合从事运维或后端开发的人员。 课程难点:     Linux命令较多,没有图形界面,对于新手理解有难点,复杂的命令也容易记混淆。

    3594 人正在学习 去看看 李强

线程:在一个程序里的一个执行路线就叫做线程。更准确的定义是:线程是一个进程内部的控制序列。
一切进程至少都有一个执行线程。
进程和线程:
进程是资源竞争的基本单位。
线程是程序执行的最小单位。、
线程共享进程数据,但也拥有自己的一部分数据:线程ID,一组寄存器,栈,errno,信号屏蔽字,调度优先级。
进程的多个线程共享
同一地址空间,因此Text Segment,Data Segment都是共享的,如果定义一个函数,在各线程中都可以访问到,各线程中都以调用,如果定义一个全局变量,在各线程中都可以访问到,初次之外,各线程还共享以下进程资源和环境。
文件描述符表
每种信号的处理方式(SIG_IGN.SIG——DFL或者自定义的信号处理函数)
当前工作目录用户id和组id
线程的优点:创建一个新线程的代价要比创建一个新进程小的多,与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多。
线程占用的资源比进程少很多。
能充分利用多处理器的可并行数量。
在等待慢速I/O操作结束的同时,程序可执行其他的计算任务。
计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现。
I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
线程的缺点
性能损失:
一个很少被外部事件阻塞的计算密集型线程往往无法与共享它的线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
健壮性降低:
编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。

缺乏访问控制:进程是访问控制的基本粒度在一个线程中调用某些OS函数会对真个进程造成影响。
编程难度提高:
编写与调试一个多线程程序比单线程程序困难得多。
int pthread_create(pthreate_t thread,const pthread_attr_t *attr,void (start_rutine)(void),void *arg)
thread:返回线程ID
attr:设置线程属性,attr为NULL表示使用默认属性。
start_routine:是个函数地址,线程启动后要执行的函数。
arg:传给线程启动函数的参数
返回值:成功返回0,失败返回错误码。

错误检查:传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。
pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)而是将错误代码通过返回值返回。
pthreads同样也提供了线程内errno变量,以支持其它使用errno的代码。对于pthreads函数的错误,建议通过返回值判定,因为读取返回值要比读取线程内的errno变量的开销更小。
进程ID和线程ID
在Linux中,目前的线程实现是Native POSIX Thread Libaray,简称NPTL。在这种实现下,线程又被称为轻量级进程,每一个用户态的线程,在内核中都对应一个调度实体,也拥有自己的进程描述符。
没有线程之前,一个进程对应内核里的一个进程描述符,对应一个进程ID。但是引入线程概念之后,情况发生了变化,一个用户进程下管理N个用户态线程,每个线程作为一个独立的调度实体在内核态都有自己的进程描述符,进程和内核的描述符一下子就变成了1:N关系,POSIX标准又要求进程内的所有线程调用getpid函数时返回相同的进程ID。
多线程的进程,又被称为线程组,线程组内的每一个线程在内核之中都存在一个进程描述符与之对应。进程描述符结构体中的pid,表面上看对应的是进程ID,其实不然,它对应的是线程ID;进程描述符中的tgid,含义是Thread Group ID,该值对应的是用户层面的进程ID
ps -L:显示线程ID,线程组内线程的个数。
强调一点:线程和进程不一样,进程有父进程的概念,但在线程组里面,所有的线程都是对等关系。
线程ID及进程地址空间布局
pthread_create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。
前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
pthread_create函数产生并标记在第一个参数指向的地址中的线程ID中。属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。
线程库NPTL提供了pthread_selt函数,可以获得线程 自身的ID。
pthread_t pthread_self(void);
pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址。
线程终止:从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit.
线程可以调用pthread_cancel终止同一进程中的另一个线程。
pthread_exit函数
void pthread_exit(void *value_ptr);
value_ptr:value_ptr不要指向一个局部比变量。
无返回值,跟进程一样,线程结束的时候无法返回到它的调用者。
需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
int pthread_cancel(pthread_t thread);
取消一个执行中的线程。
成功返回0,失败返回错误码。
线程等待与分离
为什么需要线程等待?
已经退出的线程,其空间没有释放,仍然在进程的地址空间内。
创建新的线程不会复用刚才退出线程的地址空间。
int pthread_join(pthread_t thread,void** value_ptr_
value_ptr:它指向一个指针,后者指向线程的返回值。
成功返回0,失败返回错误码。
调用该函数的线程将挂起等待,知道id为thread的线程终止,thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的。
如果thread线程通过return 返回,value_ptr所指向的单元里存放的是thread线程函数的返回值。
如果thread线程被别的线程嗲用pthread_cancel异常终止掉,value_ptr所指向的单元里存放的是常数PTHREAD_CANCELED;
如果thread线程是自己调用pthread exit终止的,valueptr所指向的单元存放的是传给pthread_exit的参数。
如果对thread线程的终止状态不感兴趣,可以传NULL给value_ptr参数。
分离线程
默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源 。从而造成系统泄漏。
如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
int pthread_detach(pthread_t thread);
可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离。
pthread_detach(pthread_self());
joinable和分离是冲突的,一个线程不能即是joinable有时分离的。
线程同步与互斥
大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个 线程,其他线程无法获得这种变量。
但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。
多个线程并发的操作共享变量,会带来一些问题。
互斥量的接口
初始化互斥量:
静态分配:
pthread_mutex_t mutex=PTHREAD_MTEX_INITIALIZER
动态分配:int pthread_mutex_init(pthred_mutex_t restrict mutex,const pthread_mutexattr_t restrict attr);
mutexL:要初始化的互斥量
attr:NULL;
销毁互斥量:使用PTHREAD_MUTEX_INITIALIZER初始化的互斥量不需要销毁。
不要销毁一个已经加锁的互斥量。
已经销毁的互斥量,要确保后面不会有线程再尝试加锁。
int pthread_mutex_destory(pthread_mutext_t *mutex);
互斥量加锁和解锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
成功返回0,失败返回错误号。
调用pthread_lock时,可能会遇到以下情况:
互斥量处于没有锁状态,该函数会将互斥量锁定,同时返回成功。
发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_lock调用会陷入阻塞,等待互斥量解锁。
条件变量:当一个线程互斥的访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。
例如一个线程访问队列时,发现队列为空,它只能等待,直到其它线程将一个节点添加到队列的这种情况就需要用到条件变量。
int ptread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
cond:要初始化的条件变量
attr:NULL
intpthread_cond_destroy(pthread_cond_t *cond)
等到条件满足
int pthread_cond_wait(pthread_cond_t*restrict cond,pthread_mutext_t *restrictx);
cond:要在这个条件变量上等待。
mutex:互斥量。
唤醒等待:
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
为什么pthread_cond_wait需要互斥量?
条件等待是线程同步间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程。
条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。没有互斥锁就无法安全的获取和修改共享数据。
POSIX信号量:POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源的目的。但可以用于线程间同步。
初始化信号量

#include<semaphore.h>
int sem_init(sem_t *sem,int pshared,unsigned int value);
pshared:0 表示线程间共享,非0表示进程间共享。
value:信号量初始值
int sem_destroy(sem_t *sem)
销毁信号量
等待信号量
int sem_wait(sem_t *sem);
发布信号量
int sem_post(sem_t *sem);
读写锁:在编写多线程的时候,有一种情况是十分常见的,那就是,有些公共数据修改的机会比较少,相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找操作,中间耗时很长,给这种代码段加锁,会极大的降低我们程序的效率。那么有没有一种方法,可以专门处理这种多读少写的情况,那就是读写锁。
注意:写独占,读共享,写锁优先级高。
读写锁接口:
int pthread_rwlock_init(pthread_rwlock_t* restrict rwlock,const pthread_rwlock_t *restrict attr);
销毁:
int pthread_rwlock_destory(pthread_rwlock_t *rwlock);
加锁和解锁
int pthread_rwlock_rdlock(pthread_rwlock_t*rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t*rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t &rwlock);

2020-03-26 10:18:15 gripex 阅读数 4
  • Linux零基础视频课程

    Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和UNIX的多用户、多任务、支持多线程和多CPU的操作系统。针对的是Linux服务器方面的知识,适合从事运维或后端开发的人员。 课程难点:     Linux命令较多,没有图形界面,对于新手理解有难点,复杂的命令也容易记混淆。

    3594 人正在学习 去看看 李强

1 线程的概念

  • 什么是线程

    Linux操作系统与Windows操作系统线程实现原理是不同的。
    Linux操作系统中线程是依托进程实现的,在类UNIX早期没有线程的概念,一直到80年代才提出线程的概念。
    
  • LWP

    light weight process轻量级的进程,本质仍是进程(在Linux环境下)
    
  • 进程与线程

    进程: 独立地址空间,拥有PCB
    线程: 也有PCB,但没有独立的地址空间(共享)
    区别:在于是否共享地址空间。独居(进程); 合租(线程)
    
    linux下:
    	线程: 最小的执行单位
    	进程: 最小分配资源单位,可看做只有一个线程的进程
    
  • linux内核线程实现原理

    类Unix系统中,早期是没有"线程"概念的,80年代才引入,借助进程机制实现了线程的概念。因此这类系统中,进程和线程关系密切
    1. 轻量级进程(light-weight process),也有PCB,创建线程使用的底层函数和进程一样,都是clone
    2. 从内核里看进程和线程是一样的,都有各自不同的PCB,但是PCB中指向内存资源的三级页表是相同的
    3. 进程可以蜕变成线程
    4. 线程可看做寄存器和栈的集合
    5. 在linux下,线程是最小的执行单位,进程是最小的分配资源单位
    
    查看LWP号:ps -Lf pid 查看执行线程的lwp号
    lwp(线程号):  CPU分配时间轮片的依据
    
  • 线程资源

    共享资源
    	1.文件描述符表
    	2. 每种信号的处理方式
    	3. 当前工作目录
    	4. 用户ID和组ID
    	5. 内存地址空间(.text/.data/.bss/heap/共享库)
    	
    非共享资源
    	1. 线程ID
    	2. 处理器现场和栈指针(内核栈)
    	3. 独立的栈空间(用户栈空间),如函数运行的栈空间
    	4. errno变量
    	5. 信号屏蔽字
    	6. 调度优先级
    
  • 线程的优、缺点

    优点: 
    	1. 提高程序并发性
    	2. 开销小
    	3. 数据通信、共享数据方便
    缺点:
    	1. 库函数,不稳定
    	2. 调试、编写困难、gdb不支持
    	3. 对信号支持不好
    	
    优点相对突出,缺点均不是硬伤,Linux下由于实现方法导致进程、线程差别不是很大
    

2 线程的控制原语 ***

  • 线程创建
    实例
  • 线程间共享全局变量
    线程默认共享数据段,代码段等地址空间,常用的是全局变量。而进程不共享全局变量,只能借助mmap
    

3 线程的属性控制(扩展知识)

	修改线程属性的方法

4 注意的事项

2019-05-12 10:01:41 qq_271334644 阅读数 61
  • Linux零基础视频课程

    Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和UNIX的多用户、多任务、支持多线程和多CPU的操作系统。针对的是Linux服务器方面的知识,适合从事运维或后端开发的人员。 课程难点:     Linux命令较多,没有图形界面,对于新手理解有难点,复杂的命令也容易记混淆。

    3594 人正在学习 去看看 李强

linux下,线程以进程的pcb模拟实现,因此linux下的线程就是pcb,是一个轻量级进程

 


说道线程概念,必须先说明进程

       进程是一个运行中的程序,在操作系统中,一个程序运行起来,程序被加载到内存中,操作系统创建一个进程描述符(进程控制块)PCB对程序的运行进行描述控制,因此进程就是pcb,在linux操作系统性下是一个 task_struct结构体

linux线程使用进行pcb模拟实现,因此linux的线程就是一个轻量级进程

如果说pcb成为了线程,那么进程就是一个线程组,一个进程中至少有一个或多个线程


因为linux下pcb是一个线程,因此线程是cpu调度的基本单位

因为程序运行的时候分配进程资源,进程(线程组)是资源分配的基本单位

进程中的所有线程公用一个虚拟地址空间,因此共享进程的代码段,数据段

    线程是一个数据的共享,因此线程之间进行通信将变得极为简单

           文件描述符

           每种信号的处理方式(SIG_IGN、SIG_DFL或者自定义的信号处理函数)

           当前工作目录

           用户id和组id

      因为每个线程都是pcb,是cpu调度的基本单位,因此线程可以同时运行,但是不会造成调用栈混乱,主要是因为每个线程都有自己独立的数据


多进程可以并行处理多任务,多线程也可以处理多任务---优缺点分析

   多线程有点:因为多线程共享虚拟地址空间

        线程间通信简单

        线程的创建/销毁成本更低

        线程的调度成本更低

        线程的执行粒度更细

多线程缺点:
        线程缺乏访问控制-----exit退出的是整个进程

        健壮性较低------线程的某个错误会导致整个进程的退出


线程控制:线程创建,线程终止,线程等待,线程分离

         操作系统没有提供直接创建线程的系统调用接口(用户实现一个线程的创建非常麻烦)

         因此大佬们就实现了一套线程控制接口----封装了线程库供用户使用

         因此我们说创建的线程是一个用户态线程,但是在内核对应有一个轻量级进程实现程序的调度运行


线程创建:

       


线程终止:

       在main函数中return,退出进程,普通线程入口函数中return只是退出调用线程

       pthread_exit       退出调用线程

       pthread_cancel      取消指定线程  -1  PTHREAD_CANCELED


线程等待:

        等待指定线程退出---获取退出线程的返回值,允许系统释放退出线程资源(避免资源泄漏---避免僵尸进程)

        线程退出也会形成僵尸线程

        处于joinable状态的线程是需要被等待的,因为处于joinable状态的线程退出后不会自动释放资源

        线程创建出来后,默认有一个属性----joinable属性--线程必须被等待,才能释放资源


线程分离:将线程的joinable属性修改位detach属性

                   被分离的线程(处于detach状态的进程)退出后将自动释放资源,不能被等待

                  pthread_detach    设置线程的分离属性


线程安全:多个执行流同时对临界资源进行操作而不会造成数据二义性

                 如何实现:同步与互斥

                 同步:资源访问的合理性---等待+唤醒

                互斥:资源访问的安全性---同一时间唯一访问


互斥的实现:互斥锁

                  互斥锁原理:只有0/1的计数器---本身的操作是原子的

                  互斥锁的操作步骤:定义--->初始化--->加锁/解锁--->销毁

                  死锁:

                  产生场景:锁资源的竞争以及加锁/解锁的顺序不当

                  产生条件:互斥+不可剥夺+请求与保持+环路等待

                  预防:破坏条件----如何破坏

                  避免:银行家算法,死锁检测算法


同步的实现:条件变量

                  条件变量实现同步就是本身提供了等待与唤醒的功能

                  操作步骤:

                  定义--->初始化

                  如何资源操作条件不具备则等待

                  如果一方添加了资源让等待的线程能操作就唤醒等待的进程

 

 

 

2018-09-03 12:23:40 xx18030637774 阅读数 71
  • Linux零基础视频课程

    Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和UNIX的多用户、多任务、支持多线程和多CPU的操作系统。针对的是Linux服务器方面的知识,适合从事运维或后端开发的人员。 课程难点:     Linux命令较多,没有图形界面,对于新手理解有难点,复杂的命令也容易记混淆。

    3594 人正在学习 去看看 李强

(1)线程(LWP)   可使用命令查看指定线程的LWP号:  ps  -Lf pid

            1)light weigh process(轻量级进程),本质上仍然是进程

                       进程:独立的地址空间,拥有PCB,最小分配资源单位,内存分配资源以进程为标准。

                       线程:没有独立的地址空间(共享),拥有PCB,最小的执行单位,CPU分配时间轮片是以线程为标准。

            2)线程和进程的联系和区别

                  1>线程也有PCB,创建线程使用的底层函数和进程一样,都是clone。

                  2>从内核里看进程和线程都是一样的,都有各自不同的PCB,但PCB中指向内存的三级页表是相同的(类似于指向相同内存的指针,指针不同,但是内存相同)。

                       三级映射:进程PCB--->页目录(4kb)-->页表---->物理页面---->内存单元

                              

                  3>进程可以退变成线程。

                  4>线程可以看做寄存器和栈的集合

           3)线程资源和优缺点

                     1)共享资源

                               文件描述符  信号处理方式   工作目录  用户id和组id      内存地址空间(除栈空间和errno变量)

                    2)独享资源

                              线程id   处理器现场和栈指针(内核栈)和独立的栈空间(用户栈 )  errno变量    信号屏蔽字  调度优先级

                    3)线程优缺点

                             优点:提高程序并发性  开销小(针对于进程)  数据通信共享数据方便(进程:IPC通信)

                             缺点:使用库函数,不稳定   调试编译困难,gdb不支持(出现在gdb之后)   对信号支持不好

(2)线程控制原语

           1)获取线程id:  pthread_t pthread_self(void);                     ---->对应进程getpid()

                         pthread_t  pthread_self(void);            返回值:成功 0     失败  无

               pthread_t:为linux下的无符号整数(%lu)

               线程ID为进程内部,识别标志,两个进程之间线程id允许相同。

         2)创建一个新线程 :pthread_create                                                --->对应进程的fork()

                    int pthread_create(pthread_t *thtrad,const pthread_attr_t *attr,void*(*start_routinue)(void *),void *arg);

        返回值   成功:0     失败:错误号(linux环境下线程的特点:失败均返回错误号,不能使用perror,而应该使用sterror函数

         参数              1:保存系统为分配的线程id

                               2:通常设置为NULL,表示使用线程的默认属性(可使用具体参数修改该参数)

                               3:函数指针,指向线程主函数(线程体)。函数运行结束,则线程结束。

                               4:主函数执行期间所使用的参数。

          使用pthread_create循环创建多个线程,每个线程打印自己是第几个被创造的线程

                1>实现和结果

               

                           

                    直接将整形转化为指针类型,如果是32位的操作系统,直接转换。如果是64位的操作系统,则是小端转大端问题,前头补零,数据不变化。

             2>代码修改:(换成取地址,符合我们正常的理解,结果有问题

                  

                    

                  如果是取地址的话,则线程去i的时候可能其他的线程也来了,此时两个线程公用全局变量,使得两个i值混乱。可以直接利用两个线程之间不共享栈的特点,直接采用值传递,而不是址传递。

           3)单个进程退出:pthread_exit函数

                  void  pthread_exit(void* retval)  参数:retval 表示线程退出状态,通常传NULL

          在多线程中 exit ,return ,pthread_exit的区别:

                 exit:进程中的所有线程退出,在线程中应该禁止使用exit.

                 return:返回到调用者那里,继续执行下一步程序。

                pthread_exit:退出单个线程

          结论:多线程环境中,应该尽量不用或者少用exit函数,取而代之用pthread_exit函数。pthread_exit或者return返回的指针所指向的内存必须是全局或者malloc分配的(不同线程不共享栈)

          4)阻塞等待线程退出,获取线程退出状态 :pthread_join                         ---->对应进程中的waitpid()

                    int  pthread_join(pthread_t thread,void **retval);     成功:0  失败:错误号

             参数:线程id  ,retavl:储存线程结束状态。

        5)线程分离:pthread_detach(常用与网路和多线程服务器)

                   int   pthread_detach(pthread_t thread)       成功:0  失败:错误号

                   线程分离:线程主动与主控线程断开联系。线程结束后,其退出状态不由其他线程获取,而是自己主动释放(不会产生僵尸进程)(也可以通过pthread_create 通过设置参数2实现线程分离)

                使用了线程分离的线程不能够在使用pthread_join,这样调用返回EINVAL错误

       6)杀死(取消)线程:pthread_cancel函数                    --->进程的kill函数

                 int pthread_cancel(pthread_t thread);           成功:0  失败:错误号

           注意:线程的取消和杀死并不是实时的,而是有一定的延时性,需要等待线程到底某个取消点或者检查点(通常是系统调用,可通过 man 7 pthread查看取消点列表,也可以通过pthread_testcancel()函数来自行设置一个取消点)

        当我们对一个已经取消的线程使用pthread_join时,返回值是-1.

         终止线程的方式:

                       1>线程主函数return 返回,不适用于主控线程。

                       2>一个线程调用pthread_cancel终止同一个进程中的另一个线程。

                       3>线程可以调用pthread_exit终止自己。

没有更多推荐了,返回首页