操作系统实践06—线程
文章目录
与线程管理相关的系统调用:
- pthread_create
- pthread_join
- pthread_mutex_lock
- pthread_mutex_unlock
- pthread_cond_wait
- pthread_cond_signal
- pthread_cond_broadcast
1.创建线程
1.1 原型
#include <pthread.h>
int pthread_create(pthread_t *tid, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
功能:
- 创建一个线程
- 新线程从start_routine开始执行
- 新线程的ID保存在tid指向的位置
参数 | 功能 |
---|---|
tid | 该参数是一个指针, 新线程的ID保存在tid指向的位置 |
attr | 线程属性。如果为空,则使用缺省的属性值 |
start_routine | 该参数是一个函数指针, 新线程从start_routine开始执行 |
arg | 提供给start_routine的参数 |
返回值:如果成功,返回0;如果失败,返回非0。
1.2 线程参数
创建线程时可以为线程入口函数提供参数
void *arg = "hello";
pthread_create(&tid, NULL, start_routine, arg);
线程入口函数接收类型为void *
类型的参数
void *start_routine(void *arg)
{
char *string = (char *)arg;
puts(string); // 输出hello
}
1.3 参数类型
可以向线程入口函数传递任意类型的参数
- 整型变量
int ivalue = 123;
void *arg = (void *) ivalue;
pthread_create(&tid, NULL, start_routine, arg);
- 字符串变量
char *svalue = "string";
void *arg = (void *) svalue;
pthread_create(&tid, NULL, start_routine, arg);
- 结构体变量,只能传递结构体的地址
struct person {
char *name;
int age;
} p;
void *arg = (void *) &p;
pthread_create(&tid, NULL, start_routine, arg);
1.4 例子一
// ex1.c
#include <stdio.h>
// unistd.h包含了函数sleep的声明
#include <unistd.h>
// pthread.h包含了函数pthread_create的声明
#include <pthread.h>
void *compute(void *arg)
{
char i;
for (i = 'a'; i < 'd'; i++) {
printf("worker: %c\n", i);
sleep(1);
}
return NULL;
}
int main()
{
pthread_t worker_tid;
// 使用pthread_create创建一个新的工作线程
// 现在程序中有两个线程,主线程和新创建的工作线程
// 新创建的工作线程从函数compute开始执行,工作线程在循环中打印小写的字母a、b、c,每打印一行后,调用sleep睡眠一秒
pthread_create(&worker_tid, NULL, &compute, NULL);
// 主线程调用pthread_create后继续执行,主线程在循环中打印大写的字母A、B、C,每打印一行后,调用sleep睡眠一秒
char i;
for (i = 'A'; i < 'D'; i++) {
printf("master: %c\n", i);
sleep(1);
}
return 0;
}
编译:
$ cc ex1.c
/tmp/cc7xcI6z.o: In function `main':
create.c:(.text+0x6f): undefined reference to `pthread_create'
collect2: error: ld returned 1 exit status
# 编译程序报错,编译程序找不到函数pthread_create的定义
# 函数pthread定义在pthread线程库中,加入-lpthread选项后,可以正确编译程序了
$ cc ex1.c -lpthread
$ ./a.out
master: A
worker: a
master: B
worker: b
master: C
worker: c
1.5 例子二
// ex2.c
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void *compute(void *arg)
{
char *string = arg;
int i;
for (i = 0; i < 3; i++) {
puts(string);
sleep(1);
}
return NULL;
}
int main()
{
pthread_t worker_tid;
pthread_create(&worker_tid, NULL, &compute, "worker");
compute("master");
return 0;
}
现在程序中有两个线程,主线程以"master"作为参数执行compute,新线程以"worker"作为参数执行compute。
$ cc ex2.c -lpthread
$ ./a.out
master
worker
master
worker
master
worker
2. 等待线程
2.1 原型
#include <pthread.h>
int pthread_join(pthread_t tid, void **result);
功能:等待线程结束。
参数:
参数 | 功能 |
---|---|
tid | 目标线程的ID |
result | 用于存放线程的计算结果 |
返回值:如果成功,返回0;如果失败,返回非0。
2.2 线程返回值
线程入口函数返回类型为void **
类型的结果
void *start_routine(void *arg)
{
void *result;
...
return result;
}
等待线程函数pthread_join
获取线程的返回结果
void *result;
pthread_join(tid, &result);
2.3 例子一
计算array中这6个整数的累加和
// ex1.c
#include <stdio.h>
#include <pthread.h>
// 全局变量
int array[] = {1, 1, 1, 2, 2, 2};
#define NUMBER 6
int worker_output;
void *worker(void *arg)
{
int i;
// 计算数组前3个整数的累加和,计算结果存放在变量worker_output
for (i = 0; i < NUMBER / 2; i++)
worker_output += array[i];
return NULL;
}
int master_output;
void master()
{
int i;
// 计算数组后3个整数的累加和,计算结果存放在变量masert_output
for (i = NUMBER / 2; i < NUMBER; i++)
master_output += array[i];
}
int main()
{
pthread_t worker_tid;
int total;
// 为了加快计算速度,采用两个线程进行计算
// 第一个线程计算数组前3个整数的累加和
// 第二个线程计算数组后3个整数的累加和
pthread_create(&worker_tid, NULL, worker, NULL);
master();
// 等待子线程运行结束
// worker_tid:等待线程的tid;NULL:忽略线程的返回值
pthread_join(worker_tid, NULL);
// 只有当子线程结束后,worker_output的值才是有效的
total = master_output + worker_output;
printf("master_output = %d, worker_output = %d, total = %d\n", master_output, worker_output, total);
return 0;
}
程序编译
$ cc ex1.c -lpthread
$ ./a.out
master_output = 6, worker_output = 3, total = 9
2.4 例子二
计算数组array中全部元素的累加和。例子1使用全局变量保存线程的参数和计算结果,例子2使用局部变量保存线程的参数和计算结果。
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
int array[] = {1, 1, 1, 2, 2, 2};
#define NR_TOTAL 6
#define NR_CPU 2
#define NR_CHILD (NR_TOTAL/NR_CPU)
// array - 数组的地址
// start - 计算范围的起始位置
// end - 计算范围的结束位置
struct param {
int *array;
int start;
int end;
};
// result - 计算结果累加和
struct result {
int sum;
};
void *compute(void *arg)
{
struct param *param;
struct result *result;
int sum = 0;
int i;
param = (struct param *)arg;
for (i = param->start; i < param->end; i++)
sum += param->array[i];
// 使用malloc分配一块区域。主线程获取工作线程的计算结果后,需要将这块存储区域释放
result = malloc(sizeof(struct result));
result->sum = sum;
return result;
}
int main()
{
// 创建NR_CPU个线程,NR_CPU=2
pthread_t workers[NR_CPU];
struct param params[NR_CPU];
int i;
for (i = 0; i < NR_CPU; i++) {
struct param *param;
param = ¶ms[i];
param->array = array;
// NH_CHILD等于每个线程需要累加的数组元素数量
param->start = i * NR_CHILD;
param->end = (i + 1) * NR_CHILD;
// 确定每个线程的计算范围,然后启动线程
pthread_create(&workers[i], NULL, compute, param);
}
int sum = 0;
for (i = 0; i < NR_CPU; i++) {
struct result *result;
// 调用pthread_join获取线程的计算结果
pthread_join(workers[i], (void **)&result);
sum += result->sum;
// 线程使用malloc分配内存,用来存储计算结果;汇总计算结果后,需要将计算结果释放
free(result);
}
printf("sum = %d\n", sum);
return 0;
}
程序编译
$ cc result.c -lpthread
$ ./a.out
sum = 9
3. 线程互斥
3.1 初始化互斥量
// 原型
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能
- pthread_mutex_init初始化互斥量
- pthread_mutex_destroy释放互斥量
参数:如果attr等于NULL,则使用缺省的属性进行初始化
返回值:如果成功,返回0;如果失败,返回非0
3.2 加锁解锁
// 原型
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能
- pthread_mutex_lock将互斥量加锁
- pthread_mutex_unlock将互斥量解锁
返回值:如果成功,返回0;如果失败,返回非0
3.3 例子一:不使用互斥量
创建多个线程,对全局变量global进行并发修改
// ex1.c
#include <stdio.h>
#include <pthread.h>
volatile int global = 0;
void *compute(void *arg)
{
int i;
for (i = 0; i < 100 * 100 * 100; i++) {
global++;
}
return NULL;
}
int main()
{
int i;
pthread_t tids[3];
// 3个子线程会并发修改共享变量global
global = 0;
for (i = 0; i < 3; i++)
pthread_create(&tids[i], NULL, compute, NULL);
for (i = 0; i < 3; i++)
pthread_join(tids[i], NULL);
printf("global = %d\n", global);
return 0;
}
编译执行这个程序,每个线程对global递增一百万次,期望global的值为三百万。由于并发修改造成程序的输出结果与预期不一致。
$ cc ex1.c -lpthread
$ ./a.out
global = 2971755
$ ./a.out
global = 2843321
3.4 使用互斥量
// ex2.c
#include <stdio.h>
#include <pthread.h>
pthread_mutex_t mutex;
volatile int global = 0;
void *compute(void *arg)
{
int i;
for (i = 0; i < 100 * 100 * 100; i++) {
// 线程在访问global变量前先对互斥量加锁
pthread_mutex_lock(&mutex);
global++;
// 线程在访问global变量后再对互斥量解锁
pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main()
{
int i;
pthread_t tids[3];
// 使用互斥量进行保护
// 程序开始使用pthread_mutex_init初始化mutex
pthread_mutex_init(&mutex, NULL);
for (i = 0; i < 3; i++)
pthread_create(&tids[i], NULL, compute, NULL);
for (i = 0; i < 3; i++)
pthread_join(tids[i], NULL);
// 程序结束使用pthread_mutex_destroy释放mutex
pthread_mutex_destroy(&mutex);
printf("global = %d\n", global);
return 0;
}
编译程序
$ cc ex2.c -lpthread
$ ./a.out
global = 3000000
$ ./a.out
global = 3000000
4. 条件变量
4.1 初始化
// 原型
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *attr);
int pthread_cond_destroy(pthread_cond_t *cond);
功能
- pthread_cond_init初始化条件变量
- pthread_cond_destroy释放条件变量
参数:如果attr等于NULL,则使用缺省的属性进行初始化
返回值:如果成功,返回0;如果失败,返回非0
4.2 等待
// 原型
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
功能:阻塞当前线程的运行
参数:
参数 | 功能 |
---|---|
cond | 当前线程在条件变量上阻塞 |
mutex | 当前线程阻塞时所在的临界区 |
返回值:如果成功,返回0;如果失败,返回非0
4.3 唤醒线程
// 原型
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
功能
- pthread_cond_signal唤醒阻塞在条件变量上的一个线程
- pthread_cond_broadcast唤醒阻塞在条件变量上的所有线程
返回值:如果成功,返回0;如果失败,返回非0
4.4 共享缓冲区
变量out为共享缓冲区的读指针,变量in为共享缓冲区的写指针。初始化时,缓冲区的内容为空,此时in指针和out指针均为0
向缓冲区加入数据A,把数据A存入in指针指向的位置,in指针指向下一个位置。
向缓冲区加入数据B,把数据B存入in指针指向的位置,in指针指向下一个位置。
向缓冲区加入数据C,把数据C存入in指针指向的位置,in指针指向下一个位置。此刻缓冲区的状态为满,数组的长度为4,实际存储的元素的数目为3。
向缓冲区加入数据D,把数据D存入in指针指向的位置,in指针指向下一个位置。此时,in指针和out指针指向相同的位置,这个状态为非法状态。
4.5 例子一
使用条件变量实现生产者与消费者问题,存在一个共享缓冲区,生产者线程向共享缓冲区写数据,消费者线程从共享缓冲区读数据。
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#define CAPACITY 4
int buffer[CAPACITY];// 使用长度为4的数组表示共享缓冲区
int in;//变量in为共享缓冲去的写指针
int out;//变量out为共享缓冲去的读指针
// in指针和out指针相同时,缓冲区为空
int buffer_is_empty()
{
return in == out;
}
//n指针和out指针相邻时,缓冲区为满
int buffer_is_full()
{
return (in + 1) % CAPACITY == out;
}
int get_item()
{
int item;
// 获取out指针指向的元素
item = buffer[out];
// 移动out指针指向下一项
out = (out + 1) % CAPACITY;
return item;
}
void put_item(int item)
{
// 将元素放置在in指针指向的位置
buffer[in] = item;
// 移动in指针指向下一项
in = (in + 1) % CAPACITY;
}
pthread_mutex_t mutex;//使用mutex实现互斥访问共享变量in/out
pthread_cond_t wait_empty_buffer;//生产者需要等待条件变量wait_empty_buffer
pthread_cond_t wait_full_buffer;//消费者需要等待条件变量wait_full_buffer
#define ITEM_COUNT (CAPACITY * 2)
void *consume(void *arg)
{
int i;
int item;
for (i = 0; i < ITEM_COUNT; i++) {
pthread_mutex_lock(&mutex);
// 缓冲区为空,消费者等待条件变量wait_full_buffer
while (buffer_is_empty())
pthread_cond_wait(&wait_full_buffer, &mutex);
item = get_item();
printf(" consume item: %c\n", item);
// 释放一个空缓冲区,唤醒等待空缓冲区的生产者
pthread_cond_signal(&wait_empty_buffer);
pthread_mutex_unlock(&mutex);
}
return NULL;
}
void *produce(void *arg)
{
int i;
int item;
for (i = 0; i < ITEM_COUNT; i++) {
pthread_mutex_lock(&mutex);
// 缓冲区为满,生产者等待条件变量wait_empty_buffer
while (buffer_is_full())
pthread_cond_wait(&wait_empty_buffer, &mutex);
item = 'a' + i;
put_item(item);
printf("produce item: %c\n", item);
// 释放一个满缓冲区,唤醒等待满缓冲区的消费者
pthread_cond_signal(&wait_full_buffer);
pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main()
{
pthread_t consumer_tid;
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&wait_empty_buffer, NULL);
pthread_cond_init(&wait_full_buffer, NULL);
// 创建消费者线程
pthread_create(&consumer_tid, NULL, consume, NULL);
// 主线程作为生产者,执行produce函数
produce(NULL);
// 等待消费者线程结束
pthread_join(consumer_tid, NULL);
return 0;
}
编译输出如下
$ cc ex1.c -lpthread
$ ./a.out
produce item: a
produce item: b
produce item: c
consume item: a
consume item: b
consume item: c
produce item: d
produce item: e
produce item: f
consume item: d
consume item: e
consume item: f
produce item: g
produce item: h
consume item: g
consume item: h
4.6 例子二
实现功能同4.5,使用信号量完成同步。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#define CAPACITY 4
int buffer[CAPACITY];//使用长度为4的数组表示共享缓冲区
int in;//变量in为共享缓冲去的写指针
int out;//变量out为共享缓冲去的读指针
// in指针和out指针相同时,缓冲区为空
int buffer_is_empty()
{
return in == out;
}
// in指针和out指针相邻时,缓冲区为满
int buffer_is_full()
{
return (in + 1) % CAPACITY == out;
}
int get_item()
{
int item;
item = buffer[out];
out = (out + 1) % CAPACITY;
return item;
}
void put_item(int item)
{
buffer[in] = item;
in = (in + 1) % CAPACITY;
}
// 使用条件变量实现信号量sema_t,value记录了信号量的值
typedef struct {
int value;
pthread_mutex_t mutex;
pthread_cond_t cond;
} sema_t;
void sema_init(sema_t *sema, int value)
{
sema->value = value;
pthread_mutex_init(&sema->mutex, NULL);
pthread_cond_init(&sema->cond, NULL);
}
// 如果信号量的值小于等于0,则等待条件变量
void sema_wait(sema_t *sema)
{
pthread_mutex_lock(&sema->mutex);
while (sema->value <= 0)
pthread_cond_wait(&sema->cond, &sema->mutex);
// 将信号量的值减一
sema->value--;
pthread_mutex_unlock(&sema->mutex);
}
void sema_signal(sema_t *sema)
{
pthread_mutex_lock(&sema->mutex);
// 将信号量的值加一
++sema->value;
// 唤醒等待条件变量的线程
pthread_cond_signal(&sema->cond);
pthread_mutex_unlock(&sema->mutex);
}
sema_t mutex_sema;//mutex_sema用于互斥访问共享缓冲区变量in/out
// empty_buffer_sema/full_buffer_sema用于线程同步
sema_t empty_buffer_sema;
sema_t full_buffer_sema;
#define ITEM_COUNT (CAPACITY * 2)
void *consume(void *arg)
{
int i;
int item;
// 消费者线程,从缓冲区中获取数据并打印
for (i = 0; i < ITEM_COUNT; i++) {
// full_buffer_sema的数值表示空buffer的数量,申请请信号量full_buffer_sema
sema_wait(&full_buffer_sema);
sema_wait(&mutex_sema);
item = get_item();
printf(" consume item: %c\n", item);
sema_signal(&mutex_sema);
// 消费者线程取走一个数据后,释放信号量empty_buffer_sema
sema_signal(&empty_buffer_sema);
}
return NULL;
}
void *produce()
{
int i;
int item;
// 生产者线程,从缓冲区中获取数据并打印
for (i = 0; i < ITEM_COUNT; i++) {
// empty_buffer_sema的数值表示空buffer的数量,申请请信号量empty_buffer_sema
sema_wait(&empty_buffer_sema);
sema_wait(&mutex_sema);
item = i + 'a';
put_item(item);
printf("produce item: %c\n", item);
sema_signal(&mutex_sema);
// 生产者线程生产一个数据后,释放信号量full_buffer_sema
sema_signal(&full_buffer_sema);
}
return NULL;
}
int main()
{
pthread_t consumer_tid;
// 初始化信号量
sema_init(&mutex_sema, 1);
sema_init(&empty_buffer_sema, CAPACITY - 1);
sema_init(&full_buffer_sema, 0);
// 创建消费者线程
pthread_create(&consumer_tid, NULL, consume, NULL);
// 主线程作为生产者,执行produce函数
produce();
// 等待消费者线程结束
pthread_join(consumer_tid, NULL);
return 0;
}
编译程序如下
$ cc ex2.c -lpthread
$ ./a.out
produce item: a
produce item: b
produce item: c
consume item: a
consume item: b
consume item: c
produce item: d
produce item: e
produce item: f
consume item: d
consume item: e
consume item: f
produce item: g
produce item: h
consume item: g
consume item: h