8.线程与互斥量

[亡魂溺海] 提交于 2020-01-16 11:06:40
一.基本概念
 进程中有哪些资源:代码段指令,只读段,全局段,静态数据,段,堆,栈,命令行参数,环境变量表,代码的执行者(线程)。
 线程:在进程中,负责执行代码的一个单位,它是进程的一部分,一个进程至少要有一个线程(主线程),进程也可以有多个线程(创建)
 线程中的代码段指令,只读段,全局段,静态数据,段,堆,命令行参数,环境变量表,文件描述符,信号处理函数,等资源共享
  线程之间,栈空间是私有的
 线程是进程的一个实体,是操作系统独立调度和分派任务的基本单位。
二.POSIX线程
 Unix和Linux是天生骄傲(不支持线程),通过添加额外的线程库可以使用,在编译多线程代码时需要添加 -lpthread,头文件pthread.h
 对线程的操作:
  创建线程
  销毁线程
  分离线程
  联合线程
  查询线程属性
  设置线程属性
    对于线程来说,最重要的是解决脏数据问题(线程同步),对于进程来说,解决通信问题(IPC)
三.创建线程
       int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);
       功能:创建线程
       thread:返回值,获取线程id
       attr:参数,设置线程属性
       start_routine:参数,线程的入口函数
       arg:给线程入口函数的参数
 1、同一个进程的多个线程都在同一个地址空间内活动,因此相对于进程,线程的系统开销小,任务切换快,它们可以执行相同的代码,也可以执行不同的代码。
 2、线程间的数据交换不需要依赖于类似IPC的特殊通信机制,简单而高效,每个线程拥有自己独立的线程ID、寄存器信息、函数栈、错误码和信号掩码,线程之间存在优先级的差异。
 3、main函数即主线程,main函数返回即主线程结束,主线程结束即进程结束,进程一但结束其所有的线程即结束。
 4、应设法保证在线程过程函数执行期间,其参数所指向的目标持久有效。
四、对线程的操作
 1、等待线程
  int pthread_join (pthread_t thread, void** retval);
  功能:等待thread参数所标识的线程结束
  retval:返回值,线程入口函数的返回值
  返回值:成功返回0,失败返回错误码。
  线程过程函数将所需返回的内容放在一块内存中,返回该内存的地址,要保证这块内存在函数返回后,即线程结束,以后依然有效;
  若retval参数非NULL,则pthread_join函数将线程过程函数所返回的指针,拷贝到该参数所指向的内存中;
  若线程过程函数所返回的指针指向动态分配的内存,则还需保证在用过该内存之后释放之。
 2、获取线程id
  pthread_t pthread_self (void);
  功能:返回当前线程的id,此函数不会执行失败。
 3、比较两个线程
  int pthread_equal (pthread_t t1, pthread_t t2);
  功能:比较两个id是否是同一个线程
  返回值:两个id相等,则返回非零,否则返回0。
  某些实现的pthread_t不是unsigned long int类型,可能是结构体类型,无法通过“==”判断其相等性。
 4、终止线程
  1) 从线程过程函数中return。
  2) 调用pthread_exit函数。
  void pthread_exit (void* retval);
  retval和线程过程函数的返回值语义相同。
 5、线程的执行轨迹
  1) 同步方式(非分离状态):
  创建线程之后调用pthread_join函数等待其终止,并释放线程资源。
  2) 异步方式(分离状态):
  无需创建者等待,线程终止后自行释放资源。
  int pthread_detach (pthread_t thread);
  功能:使线程进入分离(DETACHED)状态。
  返回值:成功返回0,失败返回错误码。
  处于分离状态的线程终止后自动释放线程资源,且不能被pthread_join函数等待。
 6、取消线程
  1) 向指定线程发送取消请求
  int pthread_cancel (pthread_t thread);
成功返回0,失败返回错误码。
注意:只是向线程发出取消请求,并不等待线程终止。
  缺省情况下,线程在收到取消请求以后,并不会立即终止,而是仍继续运行,直到其达到某个取消点。
  在取消点处,线程检查其自身是否已被取消了,并做出相应动作。
  当线程调用一些特定函数时,取消点会出现。
  2) 设置调用线程的可取消状态
  int pthread_setcancelstate (int state,int* oldstate);
  成功返回0,并通过oldstate参数输出原可取消状态(若非NULL),失败返回错误码。
  state取值:
      PTHREAD_CANCEL_ENABLE:接受取消请求
      PTHREAD_CANCEL_DISABLE:忽略取消请求。
  3) 设置调用线程的可取消类型
  int pthread_setcanceltype (int type, int* oldtype);
  成功返回0,并通过oldtype参数输出原可取消类型(若非NULL),失败返回错误码。
  type取值:
     PTHREAD_CANCEL_DEFERRED延迟取消(缺省)。
       被取消线程在接收到取消请求之后并不立即响应,
        而是一直等到执行了特定的函数(取消点)之后再响应该请求。
     PTHREAD_CANCEL_ASYNCHRONOUS - 异步取消。
       被取消线程可以在任意时间取消,不是非得遇到取消点才能被取消。
        但是操作系统并不能保证这一点。
       练习:悟空聊天室
        服务端:
         1.使用tcp协议进行网络通信
         2.为每一个客户端创建一个线程进行服务
         3.收到某个客户端的消息,转发给其他客户端(除了它自己)
   4.服务器最大在线人数50个
        客户端:
         1.输入昵称
         2.一个线程发送消息
         3.另一个线程接收消息


五、竞争与同步
 当多个线程同时访问其所共享的进程资源时,需要相互协调,以防止出现数据不一致、不完整的问题。这就叫线程同步。
 
六、互斥量、
 
 int pthread_mutex_init (pthread_mutex_t* mutex,const pthread_mutexattr_t* mutexattr);
 功能:初始化互斥量
 //亦可 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
 int pthread_mutex_lock (pthread_mutex_t* mutex);
 功能:加锁
 int pthread_mutex_unlock (pthread_mutex_t* mutex);
 功能:解锁
 int pthread_mutex_destroy (pthread_mutex_t* mutex);
 功能:销毁互斥量
 
 1) 互斥量被初始化为非锁定状态;
 2) 线程1调用pthread_mutex_lock函数,立即返回,互斥量呈锁定状态;
 3) 线程2调用pthread_mutex_lock函数,阻塞等待;
 4) 线程1调用pthread_mutex_unlock函数,互斥量呈非锁定状态;
 5) 线程2被唤醒,从pthread_mutex_lock函数中返回,互斥量呈锁定状态;
 
七、信号量
 信号量是一个计数器,用于控制访问有限共享资源的线程数。
 注意:线程使用的信号量不在pthread.h中,而是semaphore.h
 
 // 创建信号量
 int sem_init (sem_t* sem, int pshared,unsigned int value);
 sem - 信号量ID,输出。
pshared - 一般取0,表示调用进程的信号量。
          非0表示该信号量可以共享内存的方式,
          为多个进程所共享(Linux暂不支持)。
 value - 信号量初值。
 // 信号量减1,不够减即阻塞
 int sem_wait (sem_t* sem);
 // 信号量减1,不够减即返回-1,errno为EAGAIN
 int sem_trywait (sem_t* sem);
 // 信号量减1,不够减即阻塞,
 // 直到abs_timeout超时返回-1,errno为ETIMEDOUT
 int sem_timedwait (sem_t* sem,const struct timespec* abs_timeout);
 struct timespec {
     time_t tv_sec; // Seconds
     long tv_nsec; // Nanoseconds [0 - 999999999]
 };
 // 信号量加1
 int sem_post (sem_t* sem);
 // 销毁信号量
 int sem_destroy (sem_t* sem);
 
 练习:指针图书馆有5本《大师兄语录》,创建20个线程,每个线程去借阅这本书的阅读时间(0~10)然后还书
 
八、死锁
 使用两把锁保护一个资源
 创建:锁A、锁B
 线程A 线程B
 加锁A 加锁B
 s1 s1
 加锁B 加锁A
 解锁A 解锁B
 解锁B 解锁A
 
 练习:使用互斥量实现一个死锁程序,思考如何避免死锁。
 避免途径:不要连续的加锁,(其实一个资源最好只用一个锁)
 
九.生产者和消费者模型
 一线程负责生产数据,另一部分负责消费数据
 问题1:如果生产的快,消费的慢,生产者容易撑死
 问题2:如果生产的慢,消费的快,消费者容易饿死
 只有把问题1和问题2都处理好了才能最大限度的提高效率。
 生产者快 -> 数据池满 -> 生产者休眠 -> 消费者全部开始消费 -> 数据池空 -> 消费者暂停 -> 生产者全部开始生产
十、条件变量
 条件变量可以让调用线程在满足特定条件的情况下暂停。
 int pthread_cond_init (pthread_cond_t* cond,const pthread_condattr_t* attr);
 //亦可pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
 // 使调用线程睡入条件变量cond,同时释放互斥锁mutex
 int pthread_cond_wait (pthread_cond_t* cond,pthread_mutex_t* mutex);
 // 使调用线程睡入条件变量cond,同时释放互斥锁mutex,并在时间到了之后即使没有被唤醒,也醒过来
 int pthread_cond_timedwait (pthread_cond_t* cond,
     pthread_mutex_t* mutex,
     const struct timespec* abstime);
 struct timespec {
     time_t tv_sec; // Seconds
     long tv_nsec; // Nanoseconds [0 - 999999999]
 };
 // 从条件变量cond中唤出一个线程,
 // 令其重新获得原先的互斥锁
 int pthread_cond_signal (pthread_cond_t* cond);
 注意:被唤出的线程此刻将从pthread_cond_wait函数中返回,
 但如果该线程无法获得原先的锁,则会继续阻塞在加锁上。
 // 从条件变量cond中唤出所有线程
 int pthread_cond_broadcast (pthread_cond_t* cond);
//销毁条件变量
 int pthread_cond_destroy (pthread_cond_t* cond);




易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!