备课,复习 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
程序的第一步就是导入头文件
#include <pthread.h> // pthread api
#include <unistd.h> // posix system 中通用的api
#include <semaphore.h> // 信号量操作相关
使用 gcc 编译的时候需要加上 -lpthread
参数
gcc demo1.c -lpthread -o demo1
进程创建
进程创建可以使用以下函数
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
获取当前线程的标识符。
pthread_t tid = pthread_self();
结束进程
当一个进程可以通过return
或者 pthread_exit()
方式结束自身的运行。
一个线程结束,只是说它结束了运行状态,但是并不意味着它的所有信息都已经消失。
可以通过 调用 pthread_cancel
函数结束其他进程,但前提是需要获得需要结束进程的进程标识符。
pthread_cancel(pthread_t)
进程同步
进程同步算是OS 里面比较重要的内容了,pthread
提供了很多 API 供我们使用。
pthread_join(pthread_t tid, void* revalue)
可以阻塞当前线程并等待一个线程运行完毕再重新运行该进程。revalue
是进程运行结束所返回的结果。
mutex
为了解决多个线程同时访问同一个内存问题,常用的mutex
互斥锁方式可以通过定义一个pthread_mutex_t
变量解决,它的使用方式如下。
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操作。
//生产者消费者问题
#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;
}