Linux系统编程 ---- 多线程详解

多线程

线程(LWP: light weight process),在Linux环境下线程的本质仍是进程,底层并没有一个专门的结构体来描述一个线程。也叫轻量级的进程。每个进程至少有一个执行流,也就是一个线程。


概述

进程,硬盘上的程序运行以后,会在内存空间里形成一个PCB(进程控制块),每个进程拥有自己的虚拟地址空间(mm_struct)。进程是承当操作系统资源分配的实体,线程是CPU的最小执行单元
线程存在于进程当中(进程可以认为是线程的容器),进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
每个进程拥有自己的地址空间,线程间共享进程的地址空间,进程里的大部分资源,线程都有权访问。



线程的历史

Linux最初在内核中并不能真正支持线程。但是它可以通过clone()系统调用将进程作为可调度的实体。这个调用创建了调用进程(calling process)的一个拷贝,这个拷贝与调用进程共享相同的地址空间。LinuxThreads项目使用这个调用来完全在用户空间模拟对线程的支持。不幸的是,这种方法有一些缺点,尤其是在信号处理、调度和进程间同步原语方面都存在问题。另外,这个线程模型也不符合POSIX的要求。
要改进LinuxThreads,非常明显我们需要内核的支持,并且需要重写线程库。有两个相互竞争的项目开始来满足这些要求。一个包括IBM的开发人员的团队开展了NGPT (Next-Generation POSIX Threads)项目。同时,Red Hat的一些开发人员开展了NPTL项目。NGPT在2003年中期被放弃了,把这个领域完全留给了NPTL。
NPTL,或称为Native POSIX Thread Library,是Linux线程的一个新实现,它克服了LinuxThreads的缺点,同时也符合POSIX的需求。与LinuxThreads相比,它在性能和稳定性方面都提供了重大的改进。

查看线程库版本: getconf GNU_LIBPTHREAD_VERSION



线程的用途

  • 合理使用多线程,能提高CPU密集型程序的执行效率
  • 能提高I/O密隼型程序的用户体验 (比如网页加载,需要多个线程负责网页渲染,图片下载,响应交互。。

线程的私有数据:

  • 线程ID
  • 一组寄存器。栈
  • errno
  • blocked表(信号屏蔽字)
  • 调度优先级(pri)

线程间共享

  • 同一地址空间,代码段、数据段都是共享的,如果定义一个函数,在各钱程中都可以调用,如果定义一个全局变量,在各线程中都可以访问。
  • 文件描述符表
  • handler表 (信号的处理方式)
  • 当前工作目录
  • 用户id和组id


线程的优缺点


优点

  • 创建—个新线程的代价要比创建—个新进程小得多
  • 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多线程占用的资源要比进程少很多
  • 充分利用多处理器的可并行数量
  • 在等待慢速l/O操作结束的同时,程序可执行其他的计算任务

缺点

  • 库函数,不稳定
  • 调试、编写困难、不支持gdb
  • 对信号支持不好



POSIX 线程库

  • 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以"pthread_"开头
  • 要使用这些函数库,要通过引入头文件 <pthread.h>
  • 链接这些线程函数库时要加上”-lpthread"选项

查看指定LWD号:ps -Lf pid



线程操作函数


每个线程都有自己的线程号(tid),每个线程也有一个线程号。进程号在整个操作系统中是唯一的,线程号只在它所在的进程中有效。
进程号用pid_t数据类型表示,是一个非负整数。线程号则用pthread_t数据类型来表示,Linux使用无符号长整数表示。
有的系统在实现pthread_t的时候,用一个结构体来表示,所以在可移植的操作系统实现不能把它做为整数处理。


pthread_create

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);
       Compile and link with -pthread.

功能:
创建一个线程。
参数:
thread:线程号的地址
attr:线程属性结构体地址,通常设置为NULL。start_routine:线程函数的入口地址。
arg:传给线程函数的参数。
返回值: 成功:0 失败::非0


pthread_equal

NAME
       pthread_equal - compare thread IDs
SYNOPSIS
       #include <pthread.h>
       int pthread_equal(pthread_t t1, pthread_t t2);
       Compile and link with -pthread.
DESCRIPTION
       The pthread_equal() function compares two thread identifiers.
RETURN VALUE
       If the two thread IDs are equal, pthread_equal() returns a nonzero value; otherwise, it returns 0.

判断两个线程是否相等,相等返回非0,不等返回0。
尽量使用函数比较,为了可移植性。



pthread_join

NAME
       pthread_join - join with a terminated thread
SYNOPSIS
       #include <pthread.h>
       int pthread_join(pthread_t thread, void **retval);
       Compile and link with -pthread.

功能:

等待线程结束(此函数会阻塞),并回收线程资源,类似进程的 wait()函数。如果线程已经结束,那么该函数会立即返回。
参数:
thread:被等待的线程号。
retval :用来存储线程退出状态的指针的地址。返回值:
成功:0 失败:非0

调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的:
1.如果thread线程通过return返回,retval所指向的单元里存放的是thread线程函数的返回值。
2.如果thread线程被别的线程调用pthread_cancel异常终止掉,retval所指向的单元里存放的是常数PTHREAD_CANCELED(-1)。
3.如果thread线程是自己调用pthread_exit终止的,retval所指向的单元存放的是传给pthread_exit的参数。



pthread_cancel

NAME
       pthread_cancel - send a cancellation request to a thread
SYNOPSIS
       #include <pthread.h>
       int pthread_cancel(pthread_t thread);
       Compile and link with -pthread.

功能:
取消一个线程,这个操作并不是实时的,在用户态转内核态的时候进行。例如调用系统调用函数。
参数:
thread:线程号。
返回值:成功返回0 错误:设置error



pthread_detach

NAME
       pthread_detach - detach a thread
SYNOPSIS
       #include <pthread.h>
       int pthread_detach(pthread_t thread);
       Compile and link with -pthread.

一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取它的状态为止。但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。
不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL错误。



pthread_exit

NAME
       pthread_exit - terminate calling thread
SYNOPSIS
       #include <pthread.h>
       void pthread_exit(void *retval);
       Compile and link with -pthread.

功能:
退出调用线程。一个进程中的多个线程是共享该进程的数据段,因此,通常线程退出后所占用的资源并不会释放。和return等价
参数:
retval:存储线程退出状态的指针。
返回值:void

如果主线程默认return退出,整个进程结束,所有线程都随之销毁,想要主线程退出而其他线程继续运行,main函数退出要使用 pthread_exit() 函数退出。



小结

  • 避免僵尸线程,新线程运行结束需要回收资源,使用pthread_join和 pthread_detach 防止僵尸线程。

  • 被join线程可能在join函数返回前就释放完自己的所有内存资源,所以不应当返回被回收线程栈中的值;

  • malloc和mmap申请的内存可以被其他线程释放

  • 避免在多线程模型中调用fork,除非马上exec,子进程中只有调用fork的线程存在,其他线程t在子进程中均pthread_exit

  • 信号对多线程不友好,避免在多线程程序中使用信号。



线程的互斥

  • 临界资源:多线程执行流共享的资源就叫做临界资源
  • 临界区:每个线程内部,访问临界资源的代码,就叫做临界区
  • 互斥:任何时刻,互斥保证有且只有一个执行流进入临界区访问临界资源,通常对临界资源起保护作用。
  • 原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成

互斥量

大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量。
但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。
。多个线程并发的操作共享变量,会带来—些问题。

互斥的概念:

是指散步在不同任务之间的若干程序片断,当某个任务运行其中一个程序片段时,其它任务就不能运行它们之中的任一程序片段,只能等到该任务运行完这个程序片段后才可以运行。最基本的场景就是:一个公共资源同时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。

同步的概念:

同步:是指散步在不同任务之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。最基本的场景就是:两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。比如A任务的运行依赖于B任务产生的数据。

同步可以认为是一种更为复杂的互斥,而互斥是一种特殊的同步。互斥是两个任务之间不可以同时运行,他们会相互排斥,必须等待一个线程运行完毕,另一个才能运行。而同步也不能同时运行,但他是必须要按照某种次序来运行相应的线程。因此互斥具有唯一性和排它性,但互斥并不限制任务的运行顺序,即任务是无序的,而同步的任务之间则有顺序关系。


互斥锁(mutex)

互斥锁(mutex),互斥锁是一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即加锁( lock )和解锁( unlock )。

互斥锁的操作流程如下:

  • 在访问共享资源后临界区域前,对互斥锁进行加锁。
  • 在访问完成后释放互斥锁导上的锁。
  • 对互斥锁进行加锁后,任何其他试图再次对互斥锁加锁的线程将会被阻塞,直到锁被释放。

在这里插入图片描述

如果此时一个线程正在执行临界区代码,刚好CPU执行时间片到了,或者来了一个高优先级的线程,这个线程被剥离了,但这个线程依旧持有这把锁,其他线程无法访问临界区。



mutex 相关函数


pthread_mutex_init

用来初始化互斥锁。

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);

功能:
初始化一个互斥锁。
参数:
mutex:互斥锁地址。类型是pthread_mutex_t 。
attr:设置互斥量的属性,通常可采用默认属性,即可将attr设为NULL。
可以使用宏PTHREAD_MUTEX_INITIALIZER静态初始化互斥锁,比如:pthread_mutex_tmutex = PTHREAD_MUTEX_INITIALIZER;
这种方法等价于使用NULL指定的 attr参数调用pthread_mutex_init()来完成动态初始化,不同之处在于PTHREAD_MUTEX_INITIALIZER宏不进行错误检查。
返回值:成功:0,成功申请的锁默认是打开的。 失败:非0错误码



pthread_mutex_destroy

#include <pthread.h>
int pthread_mutex_destroy(pthread_mutdx_t *mutex);

功能:
销毁指定的一个互斥锁。互斥锁在使用完毕后,必须要对互斥锁进行销毁,以释放资源。
参数:
mutex:互斥锁地址。
返回值:
成功:0 失败:非0错误码



pthread_mutex_lock

#include <pthread.h>
int pthread_mutex_1ock(pthread_mutex_t *mutex);

int pthread_mutex_trylock(pthread_mutex_t *mutex);
调用该函数时,若互斥锁未加锁,则上锁,返回 0;
若互斥锁已加锁,则函数直接返回失败,即EBUSY。

功能:
对互斥锁上锁,若互斥锁已经上锁,则调用者g塞,直到互斥锁解锁后再上锁。
参数:
mutex:互斥锁地址。
返回值:
成功:0失败:非0 错误码



pthread_mutex_unlock

#include <pthread.h>
int pthread_mutex_un1ock(pthread_mutex_t *mutex);

功能:
对指定的互斥锁解锁。
参数:
mutex:互斥锁地址。
返回值:
成功:0 失败:非0错误码



线程安全试验


假设有100张票,此时有4个线程抢票。

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

int tickets = 100;// 100张票

void* func1(void* arg)
{
    
    
    while(1)
    {
    
    
        if(tickets > 0)
        {
    
    
            usleep(10000);
            printf("抢到第%d张票了,\n", tickets);
            tickets--;
        }
        else
		{
    
    
            return NULL;
        }
    }

    return NULL;
}

int main()
{
    
    
    pthread_mutex_init(&lock, NULL);
    
    pthread_t tid1, tid2, tid3, tid4;
    pthread_create(&tid1, NULL, func1, NULL);
    pthread_create(&tid2, NULL, func1, NULL);
    pthread_create(&tid3, NULL, func1, NULL);
    pthread_create(&tid4, NULL, func1, NULL);

    pthread_mutex_destroy(&lock);
    
    pthread_exit(NULL);
}

i-- 在编译后会形成多条汇编指令,如果CPU还在执行i-- 操作,此时该线程被剥离,其余线程看到的ticket的值就是错误的。此时会发生线程安全问题,这个函数为不可重入函数。
在这里插入图片描述


使用互斥锁保证数据的安全。

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

int tickets = 100;
pthread_mutex_t lock; //首先所有线程得看到同一把锁

void* func1(void* arg)
{
    
    
    while(1)
    {
    
    
        pthread_mutex_lock(&lock);//加锁
        if(tickets > 0)
        {
    
    
            usleep(10000);
            printf("抢到第%d张票了,\n", tickets);
            tickets--;
            pthread_mutex_unlock(&lock);//解锁
        }
        else{
    
    
            pthread_mutex_unlock(&lock);
            return NULL;
        }
    }

    return NULL;
}

int main()
{
    
    
    pthread_mutex_init(&lock, NULL);
    
    pthread_t tid1, tid2, tid3, tid4;
    pthread_create(&tid1, NULL, func1, NULL);
    pthread_create(&tid2, NULL, func1, NULL);
    pthread_create(&tid3, NULL, func1, NULL);
    pthread_create(&tid4, NULL, func1, NULL);
    
    pthread_mutex_destroy(&lock);
}

互斥锁的原理


使用互斥锁可以保护临界资源,但首先加锁的过程必须也是线程安全的,必须只有一个线程获得锁。

大多数架构都提供了swap或exchange指令实现互斥锁操作。该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性。


lock和unlock伪代码

lock :
	movb $0, %al
	xchgb %al, mutex
	if( al寄存器的内容>0 ) {
    
    
		return 0;
	}else
		挂起等待;
goto lock;

unlock:
	movb $1, mutex
	唤醒等待Mutex的线程;
	return 0;

获取锁的汇编指令只有一条,xchgb,作用:实现寄存器值和内存中值的交换。假设此时线程A获取锁,执行了 xchgb,此时内存中锁的值已经被替换为了0,线程B再尝试获取锁也执行xchgb,替换后得到的为0,线程B阻塞。这种操作是原子性的。

unlock的操作没有线程安全问题,因为只有一个线程能获取锁。



死锁

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

死锁的形成条件:

  • 互斥:一个资源每次只能被一个执行流使用
  • 请求与保持:—个执行流因请求资源而阻塞时,对已获得的资源保持不放
  • 不剥夺:—个执行流已获得的资源,在末使用完之前,不能强行剥夺
  • 循环等待:若干执行流之间形成—种头尾相接的循环等待资源的关系

如何避免死锁:

  • 破坏死锁的四个必要条件中的一个或多个来预防死锁。

  • 在资源动态分配过程中,用某种方式防止系统进入不安全的状态。

  • 运行时出现死锁,及时发现并处理。

  • 发生死锁后,解脱进程,通常撤销进程,回收资源,再分配给正处于阻塞状态的进程。

发生死锁

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

int s1 = 1,s2 = 1;
pthread_mutex_t lock1, lock2;

void* func1(void* arg)
{
    
    
    while(1)
    {
    
    
        pthread_mutex_lock(&lock1);       
        printf("线程1抢到lock1\n");
        pthread_mutex_lock(&lock2);       
        printf("线程1抢到lock2\n");
        pthread_mutex_unlock(&lock1);       
        pthread_mutex_unlock(&lock2);       
    }
}

void* func2(void* arg)
{
    
    
    while(1)
    {
    
    
        pthread_mutex_lock(&lock2);       
        printf("线程2抢到lock1\n");
        pthread_mutex_lock(&lock1);       
        printf("线程2抢到lock2\n"); 
        pthread_mutex_unlock(&lock2);       
        pthread_mutex_unlock(&lock1);       
    }
}


int main()
{
    
    
    pthread_t t1, t2;
    pthread_create(&t1, NULL, func1, NULL);
    pthread_create(&t2, NULL, func2, NULL);
    pthread_mutex_init(&lock1, NULL);
    pthread_mutex_init(&lock2, NULL);
    
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_mutex_destroy(&lock1);
    pthread_mutex_destroy(&lock2);
}

条件变量

与互斥锁不同,条件变量是用来等待而不是用来上锁的,条件变量本身并不是锁。
条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。

条件变量的两个动作:

  • 条件不满足,阻塞线程
  • 当条件满足,通知阻塞的线程恢复工作

条件变量相关函数

pthread_cond_init

int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);

功能:
初始化一个条件变量
参数:
cond:指向要初始化的条件变量指针。
attr:条件变量属性,通常为默认值,传NULL即可
也可以使用静态初始化的方法,初始化条件变量:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
返回值:
成功:0 失败:非0



pthread_cond_destroy

#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);

功能:
销毁一个条件变量
参数:
cond:指向要初始化的条件变量指针返回值:
成功:0 失败:非0



pthread_cond_wait

#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict (cond,
pthread_mutex_t *restrict mutex);

功能:
阻塞等待一个条件变量
参数:
cond:指向要初始化的条件变量指针mutex:互斥锁
返回值:
成功:0 失败:非0

调用该函数,会自动释放已获得的锁,被唤醒时,重新持有该锁。


pthread_cond_signal

#inelude <pthread.h>
int pthread_cond_signa1(pthread_cond_t *cond);

功能:
唤醒至少一个阻塞在条件变量上的线程
参数:
cond:指向要初始化的条件变量指针返回值:
成功:0 失败 : 非0


int pthread_cond_broadcast(pthread_cond_t *cond);

功能:
唤醒全部阻塞在条件变量上的线程
参数:
cond:指向要初始化的条件变量指针返回值:
成功:0 失败:非0


代码示例

线程B控制线程A运行。

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>


pthread_cond_t cond;
pthread_mutex_t lock;
void* func1(void* arg)
{
    
    
    while(1)
    {
    
    
        pthread_cond_wait(&cond, &lock);
        printf("消费了一个\n");
       sleep(1);
    }

}


void* func2(void* arg)
{
    
    
    while(1)
    {
    
    
        pthread_cond_signal(&cond);
        printf("生产了一个\n");
       sleep(1);
    }
}


int main()
{
    
    
    pthread_mutex_init(&lock, NULL);
    pthread_cond_init(&cond, NULL);
    pthread_t t1, t2;
    pthread_create(&t1, NULL, func1, NULL);
    pthread_create(&t2, NULL, func2, NULL)

    pthread_mutex_destroy(&lock);
    pthread_cond_destroy(&cond);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL)
}



生产者消费者模型

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。


生产者消费者的关系

生产者和生产者之间是互斥的关系,生产者和消费者是同步的,消费者和消费者互斥的。


生产者消费者模型优点

  • 实现了生产者和消费者之间的解耦
  • 并发性高


BlockingQueue生产者消费者模型

在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)

在这里插入图片描述



模拟实现

BlockQueue.hpp

#include <unistd.h>
#include <pthread.h>
#include <iostream>
#include <queue>
#include <cmath>

class BlockQueue{
    
    

private:
    std::queue<int> _que;
    const size_t _capacity = 5;
    pthread_mutex_t lock;
    pthread_cond_t cond_p;
    pthread_cond_t cond_c;
public:
    
    void Put()
    {
    
    
        while(IsFull())
        {
    
    
            WakeUpConsumer();
            ProducterWait();
        }
        
        int data = 1;
        _que.push(data);        
        std::cout << "product data" << std::endl;
    }

    int Get()
    {
    
    
        while(IsEmpty())
        {
    
    
            WakeupProducter();
            ConsumerWait(); 
        }
        
        int ret = _que.front();
        _que.pop();
        std::cout << "consume data" << std::endl;
        return ret;
    }

    bool IsEmpty()
    {
    
    
        return _que.empty();
    }

    bool IsFull()
    {
    
    
        return _que.size() >= _capacity;
    }
    
    void ConsumerWait()
    {
    
    
        pthread_cond_wait(&cond_c, &lock);
    }
    void ProducterWait()
    {
    
    
        pthread_cond_wait(&cond_p, &lock);
    }
    
    void WakeUpConsumer()
    {
    
    
        pthread_cond_signal(&cond_c);
    }
    
    void WakeupProducter()
    {
    
    
        pthread_cond_signal(&cond_p);
    }
};

main.cpp

#include "pc_model.hpp"

void* product(void* arg)
{
    
    
    BlockQueue* bq = (BlockQueue*)arg;
    while(1)
    {
    
    
        bq->Put();
    }

    return NULL;
}

void* consume(void* arg)
{
    
    
    BlockQueue* bq = (BlockQueue*)arg;
    while(1)
    {
    
    
        std::cout <<  bq->Get() << std::endl;
        usleep(60000);
    }   
    return NULL;
}

int main()
{
    
    
    BlockQueue* bq = new BlockQueue();
    pthread_t t1, t2;
    pthread_create(&t1, NULL, product, (void*)bq);
    pthread_create(&t2, NULL, consume, (void*)bq);

    pthread_exit(nullptr);
}


POSIX信号量


POSIX信号量和SystemV信号星作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。但POSIX可以用于线程间同步。|

初始化信号量

#include <semaphore.h>
int sem_init(sem_t *sem,int pshared,unsigned int value) ;
参数:
pshared :0表示线程间共享,非零表示进程间共享
value:信号量初始值

销毁信号量

int sem_destroy(sem_t *sem) ;

等待信号量

功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem) ; //P()

发布信号量

功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1int sem_post(em_t *sem) ; //v()

信号量本质上是计数器,PV操作都是原子性的。



循环队列实现生产者消费者模型

CircularQueue.hpp

#include <queue>
#include <iostream>
#include <pthread.h>
#include <semaphore.h>
#include <vector>

class CircularQueue {
    
    

private:
    const size_t _capacity;
    sem_t _blank;
    sem_t _data;
    size_t _index_d  = 0;
    size_t _index_b = 0;
    std::vector<int> _v; 
    
public:
    CircularQueue(size_t capacity):
        _capacity(capacity)
    {
    
    
        _v.resize(_capacity); 
        sem_init(&_blank, 0, _capacity); 
        sem_init(&_data, 0, 0);
    }
    ~CircularQueue()
    {
    
    
        sem_destroy(&_blank); 
        sem_destroy(&_data);
    }
        
    void Put(int x)
    {
    
    
        sem_wait(&_blank); 
        
        _v[_index_b++] = x; 
        _index_b %= _capacity; 
        std::cout << "生产了一个" << std::endl;
        sem_post(&_data);
    }

    int Get()
    {
    
     
        sem_wait(&_data); 
        int ret;
        ret = _v[_index_d++];
        _index_d %= _capacity; 

        std::cout << "消费了一个" << std::endl;
        sem_post(&_blank);
      
        return ret;
    }
};

main.cpp

#include "circular_queue.hpp"
#include <unistd.h>

using namespace std;

void* Producter(void* arg)
{
    
    

    CircularQueue* cq = (CircularQueue*)arg;
    int i = 1;
    while(1)
    {
    
    
        cq->Put(i++);
        if(i == 5)
            i = 1;
    }
    
    return NULL;
}

void* Consumer(void* arg)
{
    
    
    CircularQueue* cq = (CircularQueue*)arg;
    while(1)
    {
    
    
        cout << cq->Get() << endl;
        sleep(1);
    }
    
    return NULL;
}
int main()
{
    
    
    pthread_t t1, t2;
    CircularQueue* cq = new CircularQueue(5);
    pthread_create(&t1, NULL, Producter, cq);
    pthread_create(&t2, NULL, Consumer, cq);
    
    pthread_exit(NULL);
}


线程池

猜你喜欢

转载自blog.csdn.net/juggte/article/details/122645535