• 8.线程与互斥量


    一.基本概念
     进程中有哪些资源:代码段指令,只读段,全局段,静态数据,段,堆,栈,命令行参数,环境变量表,代码的执行者(线程)。
     线程:在进程中,负责执行代码的一个单位,它是进程的一部分,一个进程至少要有一个线程(主线程),进程也可以有多个线程(创建)
     线程中的代码段指令,只读段,全局段,静态数据,段,堆,命令行参数,环境变量表,文件描述符,信号处理函数,等资源共享
      线程之间,栈空间是私有的
     线程是进程的一个实体,是操作系统独立调度和分派任务的基本单位。
    二.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);




  • 相关阅读:
    海选女主角
    发工资咯:)
    绝对值排序
    数列有序!
    母牛的故事
    一文看懂外汇风险准备金率调整为 20%的含义
    1080i减少带宽
    为什么要采用隔行扫描?
    720P、1080P、4K是什么意思?
    VBR一次編碼 v.s 二次編碼(VBR 1-pass vs 2-pass)
  • 原文地址:https://www.cnblogs.com/LyndonMario/p/9429962.html
Copyright © 2020-2023  润新知