打印
[STM32MP1]

Linux 多线程

[复制链接]
779|10
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
kxsi|  楼主 | 2021-9-2 18:46 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
进程与线程
  在早期的操作系统中并没有线程的概念,进程是拥有资源和独立运行的最小单位,也是程序执行的最小单位。后来,随着计算机的发展,由于进程之间的切换开销较大,已经无法满足越来越复杂的程序的要求了,于是就发明了线程。
  线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的资源(Text,heap 和 global data 等)。

线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;
一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线
进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段,数据集,堆等)及一些进程级的资源(如打开文件和信
号等),某进程内的线程在其他进程不可见;
调度和切换:线程上下文切换比进程上下文切换要快得多
  要注意的是,对于多线程来说,由于同一个进程空间中存在多个栈,任何一个空白区域被填满都会导致 stack overflow 的问题。


并行与并发
  并发(Concurrency)是指同一时刻只能有一个任务得到执行,但多个任务被快速轮换执行,使得在宏观上有多个进程被同时执行的效果(宏观上并行)。其中,并发又有伪并发和真并发,伪并发是指单核处理器的并发,真并发是指多核处理器的并发。


  并行(Parallelism)是指同一时刻有多个任务同时在执行。只有在多 CPU 的情况下,才能实现并行。


Linux 线程
  在早期的类 Unix 系统中是没有线程概念, Linux 内核也只提供了 轻量级进程(light-weight process) 的支持,未实现线程模型。Linux 是一种 “多进程单线程” 的操作系统。Linux 本身只有进程的概念,而其所谓的 “线程” 本质上在内核里仍然是进程。

  当 Linux 最初开发时,在内核中并不能真正支持线程。但是它的确可以通过 clone() 系统调用,将进程作为可调度的实体。这个调用创建了调用进程(calling process)的一个拷贝,这个拷贝与调用进程共享相同的地址空间。LinuxThreads 项目使用这个调用来实现在用户空间模拟对线程的支持。

  不幸的是,这种方法有一些缺点,尤其是在信号处理、调度和进程间同步原语方面都存在问题。另外,这个线程模型也不符合 POSIX 的要求。在源码看来,就是 Linux 内核的 clone() 没有实现对 CLONE_PID 参数的支持。


使用特权

评论回复
沙发
kxsi|  楼主 | 2021-9-2 18:50 | 只看该作者
关于 LinuxThreads
  最初,Linux 中最流行的线程机制为 LinuxThreads,所采用的就是 进程与线程一对一模型,调度交给核心,而在用户级实现一个包括信号处理在内的线程管理机制。LinuxThreads 由 Xavier Leroy (Xavier.Leroy@inria.fr) 负责开发完成,并已绑定在 GLIBC 中发行。

  通过系统调用 clone() 创建子进程时,可以有选择性地让子进程共享父进程所引用的资源,这样的子进程通常称为轻量级进程。linux上的线程就是基于轻量级进程,由用户态的 pthread 库 实现。使用 pthread 以后, 在用户看来, 每一个 task_struct 就对应一个线程,而一组线程以及它们所共同引用的一组资源就是一个进程。

  但是, 一组线程并不仅仅是引用同一组资源就够了, 它们还必须被视为一个整体。对此,POSIX(Portable Operation System Interface)标准提出了如下要求:

查看进程列表的时候,相关的一组 task_struct 应当被展现为列表中的一个节点
发送给这个"进程"的信号(对应 kill 系统调用), 将被对应的这一组 task_struct 所共享, 并且被其中的任意一个"线程"处理
发送给某个"线程"的信号(对应 pthread_kill ),将只被对应的一个 task_struct 接收,并且由它自己来处理
当"进程"被停止或继续时(对应 SIGSTOP/SIGCONT 信号),对应的这一组 task_struct 状态将改变
当"进程"收到一个致命信号(比如由于段错误收到 SIGSEGV 信号),对应的这一组 task_struct 将全部退出
  在 linux 2.6 以前,pthread 线程库对应的实现使用的便是一个名叫 LinuxThreads 的 lib,linuxThreads 利用前面提到的轻量级进程来实现线程,但是对于 POSIX 提出的那些要求,linuxThreads 除了上面的第 5 点以外,都没有实现(实际上是无能为力):

如果运行了 A 程序,A 程序创建了10 个线程, 那么在 shell 下执行 ps 命令时将看到 11 个 A 进程,而不是 1 个(注意也不是 10 个);
不管是 kill 还是 pthread_kill,信号只能被一个对应的线程所接收;
SIGSTOP/SIGCONT 信号只对一个线程起作用;
  LinuxThreads 的问题,特别是兼容性上的问题,严重阻碍了 Linux 上的跨平台应用(如 Apache)采用多线程设计,从而使得 Linux 上的线程应用一直保持在比较低的水平。在 Linux 社区中,已经有很多人在为改进线程性能而努力,其中既包括用户级线程库,也包括核心级和用户级配合改进的线程库。

  最为人看好的有两个项目,一个是 RedHat 公司牵头研发的 NPTL(Native Posix Thread Library),另一个则是 IBM 投资开发的 NGPT(Next Generation Posix Threading),二者都是围绕完全兼容 POSIX 1003.1c,同时在核内和核外做工作以而实现多对多线程模型。这两种模型都在一定程度上弥补了 LinuxThreads 的缺点,且都是重起炉灶全新设计的。


使用特权

评论回复
板凳
kxsi|  楼主 | 2021-9-2 18:51 | 只看该作者
关于 NPTL
  NPTL 全称为 Native POSIX Thread Library,是 Linux 线程的一个新实现,它克服了 LinuxThreads 的缺点,同时也符合 POSIX 的需求。与 LinuxThreads 相比,它在性能和稳定性方面都提供了重大的改进。

  在技术实现上,NPTL 仍然采用 进程与线程一对一模型,并配合 glibc 和最新的 Linux Kernel 2.5.x 在信号处理、线程同步、存储管理等多方面进行了优化。和 LinuxThreads 不同,NPTL 没有使用管理线程,核心线程的管理直接放在核内进行,这也带了性能的优化。主要是因为核心的问题,NPTL 仍然不是 100% POSIX 兼容的,但就性能而言相对 LinuxThreads 已经有很大程度上的改进了。

  到了 Linux 2.6,glibc 中有了一种新的 pthread 线程库 – NPTL(Native POSIX Threading Library)。NPTL 实现了前面提到的 POSIX 的全部 5 点要求。但是,实际上,与其说是 NPTL 实现了,不如说是 Linux 内核实现了。在 Linux 2.6 中,内核有了线程组的概念, task_struct 结构中增加了一个 tgid(thread group id)字段。如果这个 task 是一个"主线程",则它的 tgid 等于pid,否则 tgid 不等于进程的 pid(即主线程的 pid)。在 clone 系统调用中,传递 CLONE_THREAD 参数就可以把新进程的 tgid 设置为父进程的 tgid(否则新进程的tgid会设为其自身的 pid)。

  有了 tgid,内核或相关的 shell 程序就知道某个 tast_struct 是代表一个进程还是代表一个线程,也就知道在什么时候该展现它们,什么时候不该展现(比如在 ps 的时候,线程就不要展现了)。而 getpid(获取进程 ID)系统调用返回的也是 tast_struct 中的 tgid,而tast_struct 中的 pid 则由 gettid 系统调用来返回。

  通常可以通过如下方法验证自己使用的系统是否支持 NPTL,存在类似以下 NPTL 行或 Native POSIX Threads Library by Ulrich Drepper et al 行表示支持:



使用特权

评论回复
地板
kxsi|  楼主 | 2021-9-2 18:52 | 只看该作者
关于 NGPT
  IBM 的开放源码项目 NGPT 在 2003 年 1 月 10 日推出了稳定的 2.2.0 版,但相关的文档工作还差很多。就目前所知,NGPT 是基于 GNU Pth(GNU Portable Threads)项目而实现的 M:N 模型,而 GNU Pth 是一个经典的用户级线程库实现。

  按照 2003 年 3 月 NGPT 官方网站上的通知,NGPT 考虑到 NPTL 日益广泛地为人所接受,为避免不同的线程库版本引起的混乱,今后将不再进行进一步开发,而今进行支持性的维护工作。也就是说,NGPT 已经放弃与 NPTL 竞争下一代 Linux POSIX 线程库标准。

其他高效线程机制
  此处不能不提到 Scheduler Activations。这个 1991 年在 ACM 上发表的多线程内核结构影响了很多多线程内核的设计,其中包括 Mach3.0、NetBSD 和商业版本 Digital Unix(现在叫 Compaq True64 Unix)。它的实质是在使用用户级线程调度的同时,尽可能地减少用户级对核心的系统调用请求,而后者往往是运行开销的重要来源。采用这种结构的线程机制,实际上是结合了用户级线程的灵活高效和核心级线程的实用性,因此,包括 Linux、FreeBSD 在内的多个开放源码操作系统设计社区都在进行相关研究,力图在本系统中实现Scheduler Activations。

多线程编程
  Linux 系统下的多线程遵循 POSIX 线程接口,称为 pthread。编写 Linux 下的多线程程序,需要使用头文件 pthread.h,连接时需要使用库 libpthread.a。因此,后面的编译必须在选项中加入 -lpthread 选项,否则提示找不到 pthread_create() 等这些函数。

  按照 POSIX 1003.1c 标准编写的程序与 Linuxthread 库相链接即可支持 Linux 平台上的多线程,在程序中需包含头文件pthread. h,在编译链接时使用命令:gcc -D -REENTRANT -lpthread xxx. c。其中 -REENTRANT 宏使得相关库函数(如 stdio.h、errno.h 中函数)是可重入的、线程安全的(thread-safe),-lpthread 则意味着链接库目录下的 libpthread.a 或 libpthread.so 文件。使用 Linuxthread 库需要 2.0 以上版本的 Linux 内核及相应版本的 C 库(libc 5.2.18、libc 5.4.12、libc 6)。


使用特权

评论回复
5
kxsi|  楼主 | 2021-9-2 18:53 | 只看该作者
“线程”控制
设置线程属性
线程属性类型为: pthread_attr_t

用 int pthread_attr_init(pthread_attr_t *attr); 初始化线程属性对象为缺省值
线程属性值如下:
scope:用来设置创建线程的争用范围

PTHREAD_SCOPE_SYSTEM:使用此值创建的线程将于系统中的其他线程争用资源
PTHREAD_SCOPE_PROCESS:默认值,使用此值创建的线程将于进程内的其他线程争用资源。
相关接口定义:

int pthread_attr_setscope(pthread_attr_t *attr, int scope);
int pthread_attr_getscope(const pthread_attr_t *attr, int *scope);
detachstate:用于设置线程处于分离状态还是可连接状态

PTHREAD_CREATE_DETACHED:线程处于分离状态,终止时,系统自动回收资源,对该线程调用pthread_detach或pthread_join将失败
PTHREAD_CREATE_JOINABLE:默认值。线程处于可连接状态,终止时,系统不会自动回收资源。需要调用 pthread_detach 或 pthread_join 回收资源。如果用了 PTHREAD_CREATE_JOINABLE,在所有线程退出前,进程不会退出。
相关接口定义:

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
stackaddr:默认值为 NULL。新线程由系统分配栈地址

stacksize:默认为 0。新线程具有系统定义的栈大小

priority:默认为 0。新线程的优先级

inheritsched:用于设置线程的继承属性

PTHREAD_INHERIT_SCHED:从父线程继承调度策略和属性
PTHREAD_EXPLICIT_SCHED:默认值。从属性对象获得调度策略和属性,新线程不继承父线程的调度优先级
相关接口定义:

int pthread_attr_setinheritsched(pthread_attr_t *attr, int inherit);
int pthread_attr_getinheritsched(const pthread_attr_t *attr, int *inherit);
schedpolicy:设置线程的调度策略,要使用此属性,inheritsched 必须为 PTHREAD_EXPLICIT_SCHED

SCHED_OTHER:系统默认策略。新线程将一直运行,直到被抢占或者直到线程阻塞或停止为止
SCHED_FIFO:先进先出策略。具有最高优先级,等待时间最长的线程将别首先执行
SCHED_RR:轮转调度,类似 FIFO,但加了时间轮片算法
相关接口定义:

int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
int pthread_attr_getschedpolicy(const pthread_attr_t *attr, int *policy);
用 int pthread_attr_destroy(pthread_attr_t *attr); 可以销毁某个已经初始化的线程属性对象


使用特权

评论回复
6
kxsi|  楼主 | 2021-9-2 18:54 | 只看该作者
线程创建
  进程被创建时,系统会为其创建一个主线程,而要在进程中创建新的线程,则可以调用 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

返回值: 若是成功建立线程返回 0,否则返回错误的编号
形式参数:
pthread_t *thread:如果成功指向新线程标识 ID,失败时,其值未知
const pthread_attr_t *attr:创建线程时的线程属性。如果设置为 NULL,将使用系统默认属性。要么为 NULL,要么在调用 pthread_create 前用 pthread_attr_init 初始化
void *(*start_routine) (void *):新线程函数的入口指针
void *arg:void *(*start_routine) (void *) 的参数。
  每个线程都有自己的线程 ID,以便在进程内区分。线程 ID 在 pthread_create 调用时回返给创建线程的调用者(由第一个参数保存);一个线程也可以在创建后使用 pthread_t pthread_self (void) ; 调用获取自己的线程ID。

等待线程结束
等待线程结束需要使用接口:int pthread_join(pthread_t th, void **thread_return);

返回值: 如果成功返回 0,反之非 0
参数:
th:被等待的线程标识符,即线程创建时返回的线程 ID。
thread_return:是一个用户定义的指针,指向一个保存等待线程的完整退出状态的区域,用来存储被等待线程的返回值。 如果为 NULL,则线程的退出状态信息丢失。
注意:
创建一个线程默认的状态是 joinable,如果一个线程结束运行但没有被 join,则它的状态类似于进程中的 Zombie Process,即还有一部分资源没有被回收(退出状态码),所以创建线程者应该调用pthread_join 来等待线程运行结束,并可得到线程的退出代码,回收其资源(类似于wait,waitpid)
一个线程只能等待连接一个其他线程
如果多个线程等待同一个线程,只有一个线程能得到正确的状态信息
有竞争关系的线程间的连接操作将返回一个错误
如果启动连接的线程被取消,则处于等待状态的线程可以被其他线程等待
如果目标线程在执行 pthread_join 前结束,则该调用不会引起任何阻塞并立即返回
一个未被连接的非独立线程在线程结束前一直占用资源,直到创建它的进程结束
分离线程
分离线程需要使用接口:int pthread_detach(pthread_t tid);

返回值: 如果成功返回 0,反之非 0
参数:
tid:指示要分离的进程 ID。
说明:
pthread_detach 用于分离可结合线程 tid。线程能够通过以 pthread_self() 为参数的 pthread_detach 调用来分离它们自己。
pthread_detach 将该子线程的状态设置为分离的(detached),如此一来,该线程运行结束后会自动释放所有资源。
取消执行线程
  取消执行线程需要使用接口 int pthread_cancel(pthread_t thread); 。它允许线程以受控方式终止进程中执行的任何线程。目标线程的可取消行状态和类型决定取消何时生效。

返回值: 如果成功返回 0,反之非 0
参数:
thread:要取消的线程标识。


使用特权

评论回复
7
kxsi|  楼主 | 2021-9-2 18:55 | 只看该作者
线程退出
线程的退出方式有三种:

执行完成后隐式退出;
由线程本身显示调用 pthread_exit (void * retval);函数退出。其中,参数 retval 用于保存线程退出状态
被其他线程用 pthread_cance (pthread_t thread);函数终止。在某线程中调用此函数,可以终止由参数 thread 指定的线程。
实例
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
pthread_t tidp, tidp2;           // 线程ID
void *thread1(void *arg)
{
    int *num;
    num = (int *)arg;     // 存放传递的参数
    printf("I‘m thread1, ID is %u\n",pthread_self());        // 打印线程自己的ID
    printf("Receive parameter is %d \n",*num);               // 打印收到的参数
    printf("I'm waitting to be cancenl!\n");
    sleep(20);                                               // 等待线程2 将它结束
    return (void *)0;                                        // 如果,线程2没有将其结束,返回0
}
void *thread2(void *arg)
{
    int error;
    printf("I'm thread2. I Will to Cancel thread %u\n",tidp);
    error = pthread_cancel(tidp);         // 取消线程 1
    if(error<0)
    {
        printf("Cancel thread %u failed!\n",tidp);
        return (void *)-1;
    }
    else
    {
        printf("Cancel thread %u sucessfully! It will not return 0!\n",tidp);  // 因为线程2将线程1取消了,所以线程1不能正常返回0
    }
    return 0;
}
int main(int argc, char *argv[])
{
    int error;
    void* r_num1,*r_num2;

    int test = 4;
    int *attr = &test;

    error = pthread_create(&tidp,NULL,thread1,(void *)attr);   // 创建新线程,并传递参数(void *)attr
    if(error)
    {
        printf("pthread_create is created is not created ... \n");
        return -1;
    }
    error = pthread_create(&tidp2,NULL,thread2,NULL);
    if(error)
    {
        printf("pthread_create is created is not created ... \n");
        return -1;
    }
    error = pthread_join(tidp2,&r_num2);           // 阻塞在此处,直到线程2返回
    error = pthread_join(tidp,&r_num1);            // 阻塞在此处,直到线程1返回
    sleep(1);
    printf("Thread1 return %d\n",(int)r_num1);  // 线程1的返回值,如果线程1被线程2 取消,则返回值不是0
    printf("Thread2 return %d\n",(int)r_num2);  // 线程2的返回值
    return 0;      
}


使用特权

评论回复
8
kxsi|  楼主 | 2021-9-2 18:56 | 只看该作者
线程通信
互斥
  互斥意味着“排它”,即两个线程不能同时进入被互斥保护的代码。Linux 下可以通过 pthread_mutex_t 定义互斥体机制完成多线程的互斥操作,该机制的作用是对某个需要互斥的部分,在进入时先得到互斥体,如果没有得到互斥体,表明互斥部分被其它线程拥有,此时欲获取互斥体的线程阻塞,直到拥有该互斥体的线程完成互斥部分的操作为止。

  互斥锁是一个二元变量,其状态为开锁和上锁。在互斥锁范围内的任何一个线程都可以对互斥锁上锁,但只有锁住该互斥变量的线程才可以开锁。但是,互斥无法限制访问者对资源的访问顺序,即访问是无序的。

互斥锁(#include <unistd.h>)

互斥锁属性
使用 pthread_mutexattr_t 声明互斥锁属性对象。互斥锁对象由两部分组成:

process-shared:share 属性,它有两个取值:

PTHREAD_PROCESS_PRIVATE:默认值
PTHREAD_PROCESS_SHARED:前者用于同步本进程的不同线程,后者用来不同进程中的线程同步。
相关接口定义:

int pthread_mutexattr_setshared(pthread_mutexattr_t *atrr, int pshared); 设置互斥锁属性
参数:
attr: 要设置的互斥锁属性对象指针
pshared: 要设置的互斥锁 share 属性
int pthread_mutexattr_getshared(pthread_mutexattr_t *atrr, int *pshared); 获得互斥锁属性
参数:
attr: 要获得的互斥锁属性对象指针
pshared: 用来保存互斥锁 share 属性的指针
type:互斥锁类型。可选的类型有

PTHREAD_MUTEX_NORMAL
PTHREAD_MUTEX_ERRORCHECK
PTHREAD_MUTEX_RECURSIVE
PTHREAD _MUTEX_DEFAULT(默认)。
它们分别定义了不同的上所、解锁机制,一般情况下,选用最后一个默认属性。相关接口定义:

int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type); 设置互斥锁类型。
参数:
attr: 要设置的互斥锁属性对象指针
type: 要设置的互斥锁类型
int pthread_mutexattr_gettype(pthread_mutexattr_t *atrr, int *type); 获得互斥锁类型。
参数:
attr: 要获得的互斥锁属性对象指针
type: 用来保存互斥锁类型的指针
互斥锁属性初始化
用 int pthread_mutexattr_init(pthread_mutexattr_t *attr); 初始化互斥锁属性对象。

参数:
attr:为待初始化的互斥锁属性对象的指针。
销毁互斥锁属性对象
用 int pthread_mutexattr_destroy(pthread_mutexattr_t *attr); 销毁互斥锁属性对象

参数:
attr:斥锁属性对象的指针
初始化互斥锁
用 int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr); 初始化互斥锁

参数:
attr:为指向要初始化的互斥锁的指针
mutexattr:指向互斥锁属性的指针,如果为 NULL,则采用默认属性
注意:
用 PTHREAD_MUTEX_INITIALIZER 宏初始化静态分配的互斥锁
销毁互斥锁
用 int pthread_mutex_destroy(pthread_mutex_t *mutex); 销毁互斥锁

参数:
mutex:要销毁的互斥锁的指针
上锁
用 int pthread_mutex_lock(pthread_mutex_t *mutex); 给互斥锁上锁

参数:
mutex:要上锁的互斥锁指针
返回值:如果成功返回0,失败返回其他
注意:
pthread_mutex_lock 声明开始用互斥锁上锁,此后的代码直至调用 pthread_mutex_unlock 为止,均被上锁,即同一时间只能被一个线程调用执行。当一个线程执行到 pthread_mutex_lock 处时,如果该锁此时被另一个线程使用,那此线程被阻塞,即程序将等待到另一个线程释放此互斥锁。
用 int pthread_mutex_trylock(pthread_mutex_t *mutex); 非阻塞锁定。如果一次非阻塞后,如果不能获取 mutex 引用的互斥对象,调用将立刻返回错误
解锁
  用 int pthread_mutex_unlock(pthread_mutex_t *mutex); 解锁互斥锁。当解锁互斥锁以后,如果有线程阻塞该互斥锁,将使用调度策略确定那个线程将获得互斥锁。

返回值:如果成功返回0,失败其他
参数:
mutex:要解锁的互斥锁指针
注意:
释放互斥锁的方式取决于 type 属性
普通话和缺省方式: 在已经解锁的互斥锁上解锁,将导致未定义行为
递归和错误检查方式: 在已经解锁的互斥锁上解锁将返回错误。对于递归方式,解锁次数必须有锁定次数一样多


使用特权

评论回复
9
kxsi|  楼主 | 2021-9-2 18:57 | 只看该作者
线程同步
  线程同步是指在互斥的基础上(大多数情况),通过其他机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是,所有写入资源的情况必定时互斥的。

条件变量(#include <unistd.h>)
  互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。

  使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线程间的同步。

条件变量属性
  设置条件变量属性对象。pthread_condattr_t 声明条件变量属性对象。条件变量属性对象由两部分组成(和互斥锁一样):

process-shared:share属性。它有两个取值

PTHREAD_PROCESS_PRIVATE(默认)
PTHREAD_PROCESS_SHARED。前者用于同步本进程的不同线程,后者用来不同进程中的线程同步。
相关接口定义:

int pthread_condattr_setshared(pthread_condattr_t *atrr, int pshared); 函数设置条件变量属性。
参数:
attr: 要设置的条件变量属性对象指针
pshared: 要设置的条件变量属性share属性
int pthread_condattr_getshared(pthread_condattr_t *atrr, int *pshared); 获得条件变量属性
参数:
attr: 要获得的条件变量属性对象指针
pshared: 用来保存条件变量属性share属性的指针
type:条件变量属性类型。可选的类型有

PTHREAD_MUTEX_NORMAL
PTHREAD_MUTEX_ERRORCHECK
PTHREAD_MUTEX_RECURSIVE
PTHREAD _MUTEX_DEFAULT(默认)。
它们分别定义了不同的上所、解锁机制,一般情况下,选用最后一个默认属性。相关接口定义:

int pthread_condattr_settype(pthread_ condattr *attr, int type); 函数设置。
参数:
attr: 要设置的件变量属性对象指针
type: 要设置的件变量属性对象类型
int pthread_condattr_gettype(pthread_condattr_t *atrr, int *type); 获得件变量属性对象属性。
参数:
attr: 要获得的件变量属性对象指针
type: 用来保存件变量属性对象类型的指针

初始化条件变量属性对象
用 int pthread_condattr_init(pthread_condattr_t *attr); 初始化条件变量属性对象

参数:
attr:为待初始化的条件变量属性对象的指针,由上面的函数设置。
销毁条件变量属性对象
用 int pthread_condattr_destroy(pthread_condattr_t *attr); 销毁条件变量属性对象

参数:
attr:条件变量属性对象的指针
初始化条件变量
用 int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr); 初始化条件变量

参数:
cond:为指向要初始化的条件变量的指针
cond_attr:是指向条件变量属性的指针,如果为NULL,则采用默认属性
注意:
用 PTHREAD_MUTEX_INITIALIZER 宏初始化静态分配的互斥锁
销毁条件变量
用 int pthread_cond_destroy(pthread_cond_t *cond); 销毁条件变量

参数:
cond:要销毁的条件变量的指针
取消阻塞一个条件变量线程
  用 int pthread_cond_signal(pthread_cond_t *cond); 取消阻塞一个条件变量线程。函数被用来释放被阻塞在指定条件变量上的一个线程。必须在互斥锁的保护下使用相应的条件变量。否则对条件变量的解锁有可能发生在锁定条件变量之前,从而造成死锁。

参数:
cond:指向已经被初始化了的条件变量的指针
返回值:如果成功返回0,失败返回其他
注意:
可以使用 int pthread_cond_broadcast(pthread_cond_t *cond); 取消阻塞所有条件变量线程
由调度策略决定 pthread_cond_signal 唤醒那个线程
由调度策略决定 pthread_cond_broadcast 的唤醒顺序
等待条件变量
用 int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); 等待条件变量

说明:
函数将解锁 mutex 参数指向的互斥锁,并使当前线程阻塞在 cv 参数指向的条件变量上。被阻塞的线程可以被 pthread_cond_signal 函数,pthread_cond_broadcast 函数唤醒,也可能在被信号中断后被唤醒。
pthread_cond_wait 函数的返回并不意味着条件的值一定发生了变化,必须重新检查条件的值。
pthread_cond_wait函数返回时,相应的互斥锁将被当前线程锁定,即使是函数出错返回。


使用特权

评论回复
10
kxsi|  楼主 | 2021-9-2 18:57 | 只看该作者
互斥锁和条件变量使用实例
// producer_consumer.cpp
//
// 有一个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,
// 在两者之间设置一个有多个缓冲区的缓冲池,生产者将它生产的产品放入一个缓冲区中,消费者可以从缓
// 冲区中取走产品进行消费,所有生产者和消费者都是异步方式运行的,但它们必须保持同步,即不允许消
// 费者到一个空的缓冲区中取产品,也不允许生产者向一个已经装满产品且尚未被取走的缓冲区中投放产品。
//
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define true 1
#define false 0
#define BUFFER_LENGTH 10
int buf[BUFFER_LENGTH];   // 缓冲区大小
int front = 0, rear = -1; // 缓冲区的前端和尾端
int size = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;        // 互斥锁 mutex  使用宏赋值
pthread_cond_t empty_cond = PTHREAD_COND_INITIALIZER;     // 条件变量 empty_cond  使用宏赋值
pthread_cond_t full_cond = PTHREAD_COND_INITIALIZER;      // 条件变量 full_cond  使用宏赋值
int producer_wait = false;
int consumer_wait = false;
void *producer(void *arg);
void *consumer(void *arg);
int main(int argc, char **argv)
{
    pthread_t producer_id;
    pthread_t consumer_id;
    pthread_create(&producer_id, NULL, producer, NULL);      // 创建生产者线程,
    pthread_create(&consumer_id, NULL, consumer, NULL);
    sleep(1);
    return 0;
}
void *producer(void *arg)
{
    pthread_detach(pthread_self());       // 分离自身,自动释放所有资源
    while (true)
    {
        pthread_mutex_lock(&mutex);       // 上锁,直到 pthread_mutex_unlock(&mutex);
        if (size == BUFFER_LENGTH)        // 如果缓冲区已满,等待; 否则,添加新产品
        {
            printf("buffer is full. producer is waiting...\n");
            producer_wait = true;
            pthread_cond_wait(&full_cond, &mutex);          // 解开互斥锁mutex,并由条件变量full_cond阻塞在此处(直到消费者调用pthread_cond_signal(&full_cond);解锁),此时消费者可以访问mutex,而不会阻塞了
            producer_wait = false;
        }
        // 往尾端添加一个产品
        rear = (rear + 1) % BUFFER_LENGTH;
        buf[rear] = rand() % BUFFER_LENGTH;
        printf("producer produces the item %d: %d\n", rear, buf[rear]);
        ++size;
        if (size == 1) // 如果当前size=1, 说明以前size=0, 消费者在等待,则给消费者发信号
        {
            while (true)
            {
                if (consumer_wait)
                {
                    pthread_cond_signal(&empty_cond);          // 如果消费者在等待,则释放消费者的阻塞状态
                    break;
                }
            }
        }
        pthread_mutex_unlock(&mutex);
        sleep(0.1);
    }
}
void *consumer(void *arg)
{
    pthread_detach(pthread_self());       // 分离自身,自动释放所有资源
    while (true)
    {
        pthread_mutex_lock(&mutex);
        if (size == 0)                   // 如果缓冲区已空,等待; 否则,消费产品
        {
            printf("buffer is empty. consumer is waiting...\n");
            consumer_wait = true;
            pthread_cond_wait(&empty_cond, &mutex);    // 解锁 mutex,并阻塞在 empty_cond(直到生产者调用pthread_cond_signal(&empty_cond);解锁),此时,生产者可以访问mutex,而不会阻塞
            consumer_wait = false;
        }
        // 从前端消费一个产品
        printf("consumer consumes an item %d: %d\n", front, buf[front]);
        front = (front + 1) % BUFFER_LENGTH;
        --size;
        if (size == BUFFER_LENGTH-1) // 如果当前size=BUFFER_LENGTH-1,说明以前生产者在等待,则给生产者发信号
        {
            while (true)
            {
                if (producer_wait)
                {
                    pthread_cond_signal(&full_cond);
                    break;
                }
            }
        }
        pthread_mutex_unlock(&mutex);
        sleep(0.1);
    }
}



使用特权

评论回复
11
kxsi|  楼主 | 2021-9-2 18:58 | 只看该作者
信号量(#include <semaphore.h>)
  信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。当公共资源增加时,调用函数 sem_post() 增加信号量。只有当信号量值大于 0 时,才能使用公共资源,使用后,函数 sem_wait() 减少信号量。函数 sem_trywait() 和函数 pthread_ mutex_trylock() 起同样的作用,它是函数 sem_wait() 的非阻塞版本。它们都在头文件 semaphore.h 中定义。

  信号量的本质是一种数据操作锁,它本身不具有数据交换的功能,而是通过控制其他的通信资源(文件,外部设备)来实现进程间通信,它本身只是一种外部资源的标识。信号量在此过程中负责数据操作的互斥、同步等功能。

信号量的数据类型为结构 sem_t,它本质上是一个长整型的数。

初始化
函数 int sem_init(sem_t *sem, int pshared, unsigned int value); 用来初始化一个信号量。

参数:
sem:为指向信号量结构的一个指针;
pshared:不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享;
value:给出了信号量的初始值。
增加信号量
函数 int sem_post(sem_t *sem); 用来增加信号量的值。当有线程阻塞在这个信号量上时,调用这个函数会使其中的一个线程不在阻塞,选择机制同样是由线程的调度策略决定的。

减少信号量
函数 int sem_wait(sem_t *sem); 用来减少信号量的值。如果参数 sem 小于等于0,线程阻塞在此。直到信号量参数 sem 的值大于 0,解除阻塞后将 sem 的值减一,表明公共资源经使用后减少。函数sem_trywait ( sem_t *sem )是函数sem_wait()的非阻塞版本,它直接将信号量 sem 的值减一。

释放信号量
函数 int sem_destroy(sem_t *sem); 用来释放信号量 sem。

读取信号量的值
用 int sem_getvalue(sem_t *sem,int *aval); 读取当前信号量的值

信号量实例
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>

sem_t sem;                   // 信号量

void *thread1();
void *thread2();
int main()
{
    pthread_t t1,t2;
    void *rtn1,*rtn2;

    sem_init(&sem,0,0);         // 初始化信号量

    pthread_create(&t1, NULL, thread1, NULL); // 创建线程
    pthread_create(&t2, NULL, thread2, NULL);

    pthread_join(t1,&rtn1);  // 等待线程结束
    pthread_join(t2,&rtn2);
}

void *thread1()
{
    while(1)
    {
        // pthread_detach(pthread_self());

        printf("Thread1:%u\n",pthread_self());
        sleep(1);
        sem_post(&sem);      // 增加信号量
    }
}

void *thread2()
{
    while(1)
    {
        //pthread_detach(pthread_self());
        sem_wait(&sem);                         // 开始,信号量值为0,线程2阻塞在此处而不能运行
        printf("Thread2:%u\n",pthread_self());
        sleep(1);
    }
}



运行结果:


使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

45

主题

3310

帖子

2

粉丝