Skip to content

备课,复习 pthread 的使用,内容包括pthread 的基础知识以及并发所需要的PV操作

前言

POSIX Threads(Portable Operating System Interface), commonly known as pthreads, is an execution model that exists independently from a language, as well as a parallel execution model. It allows a program to control multiple different flows of work that overlap in time. Each flow of work is referred to as a thread, and creation and control over these flows is achieved by making calls to the POSIX Threads API.

  • 是一种独立于编程语言的程序并行运行模型
  • 控制程序的并行工作流(线程)

准备工作

在编写pthread程序的第一步就是导入头文件

c
#include <pthread.h>	// pthread api
#include <unistd.h>		// posix system 中通用的api
#include <semaphore.h> 	// 信号量操作相关

使用 gcc 编译的时候需要加上 -lpthread参数

c
gcc demo1.c -lpthread -o demo1

进程创建

进程创建可以使用以下函数

c
int pthread_create (pthread_t *thread_id,pthread_attr_t *attr,void *(*start_routine)(void *),void *arg)
  • thread_id是进程的唯一标识符
  • attr_t 设置进程的属性,可以设置想进程的调度策略,堆栈相关信息,可以设置为默认值 NULL
  • start_routine是线程开始运行的函数指针,函数指针的原型包括一个void类型的形参指针
  • arg设置线程运行时传递的参数,可以在线程开始运行时传递用户自定义参数

在进程运行过程中可以使用pthread_self获取当前线程的标识符。

c
pthread_t tid = pthread_self();

结束进程

当一个进程可以通过return 或者 pthread_exit()方式结束自身的运行。

一个线程结束,只是说它结束了运行状态,但是并不意味着它的所有信息都已经消失。

可以通过 调用 pthread_cancel函数结束其他进程,但前提是需要获得需要结束进程的进程标识符。

c
pthread_cancel(pthread_t)

进程同步

进程同步算是OS 里面比较重要的内容了,pthread提供了很多 API 供我们使用。

pthread_join(pthread_t tid, void* revalue)可以阻塞当前线程并等待一个线程运行完毕再重新运行该进程。revalue是进程运行结束所返回的结果。

mutex

为了解决多个线程同时访问同一个内存问题,常用的mutex互斥锁方式可以通过定义一个pthread_mutex_t变量解决,它的使用方式如下。

c
pthread_mutex_t buff_mutex;
int count = 0;
void* thread1(void* arg)
{
    pthread_mutex_lock(&buff_mutex);
    // 访问内存
    buff_count++;
    pthread_mutex_unlock(&buff_mutex);
}

void* thread2(void* arg)
{
    pthread_mutex_lock(&buff_mutex);
    // 访问内存
    buff_count--;
    pthread_mutex_unlock(&buff_mutex);
}

void main(void)
{
    pthread_mutex_init(&buff_mutex, NULL);
    pthread_mutex_destroy(&buff_mutex);
    
    pthread_mutex_destroy(&buff_mutex); // 使用完成后需要销毁
    return 0;
}

在使用之前需要先调用 pthread_mutex_init初始化,当某个线程需要访问临界区时,使用 pthread_mutex_lock(&count_mutex);进入临界区,结束访问临界区后使用 pthread_mutex_unlock(&count_mutex)退出临界区访问。mutex使用完成后需要调用 pthread_mutex_destroy将mutex销毁。

semaphore

理论上mutex 可以当做一个 semaphore=1的信号量使用,在一定程度上两者是等价的,Mutex管理的是资源的访问,而Semaphore管理的是资源的数量。

信号量可以根据资源的数量控制进程的访问。例如著名的生产者消费者问题,假设缓冲区数量为N

  • 当缓冲区中的资源数目小于N,生产者就可以不断生产,但当生产的资源数等于N,生产者就要停止生产,并等待消费者进行消费。
  • 当缓冲区中资源的数目大于N,消费者就可以不断消费直到资源的数目等于0,消费者就要阻塞直到生产者生产出货物。

Linux中可以使用semaphore.h头文件中提供的API 完成PV线程同步操作。下面的代码就是实现生产者消费者问题的代码。其中sem_t定义了一个信号量,定义信号量后调用 sem_init()初始化信号量的值。 sem_wait()相当于 P操作,sem_post()相当于V操作。

c
//生产者消费者问题
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <semaphore.h>

pthread_mutex_t buff_mutex; // 创建一个互斥锁
int buff_count = 0;
int buff[10];
sem_t empty, full; // 声明信号量
// 消费者线程
void *consumer(void* arg)
{

    for (;;)
    {
        sem_wait(&full); // P(full)
        pthread_mutex_lock(&buff_mutex);
        buff[buff_count] = 0;
        buff_count--;
        printf("[consumer thread] buffer count %d", buff_count); // 打印当前的缓冲区信息
        pthread_mutex_unlock(&buff_mutex);
        sem_post(&empty); // V(empty)
        sleep(2); //假设每次消费需要 2 秒
    }
    return 0;
    
}
// 生产者线程
void *producer(void* arg)
{
    for (;;)
    {
        sem_wait(&empty); // P(empty)
        pthread_mutex_lock(&buff_mutex); // 进入临界区
        buff[buff_count] = 1;
        buff_count++;
        printf("[consumer thread] buffer count %d", buff_count); // 打印当前的缓冲区信息
        pthread_mutex_unlock(&buff_mutex); // 退出临界区
        sem_post(&full); //v(full)
        sleep(3); // 假设每次生成需要 3 秒
    }

    return 0;
}
int main(void)
{
    pthread_t p_id, c_id;

    pthread_mutex_init(&buff_mutex, NULL); // 初始化互斥锁
    sem_init(&empty, 0, 10); // 初始化,第三个参数为信号量初值
    sem_init(&full, 0, 0);
    pthread_create(&p_id, NULL, producer, NULL); // 创建生产者和消费者线程
    pthread_create(&c_id, NULL, consumer, NULL);

    pthread_join(p_id, NULL); // 阻塞当前线程,等待生产者和消费者线程执行完毕
    pthread_join(c_id, NULL);
    pthread_mutex_destroy(&buff_mutex); // 使用完后需要销毁
    sem_destroy(&empty); //销毁信号量
    sem_destroy(&full);
    return 0;
}

最新更新: