基本概念
无名信号量(也称为匿名信号量)是一个同步原语,通常用于线程之间的同步,而不是进程之间。与命名信号量(用于进程间同步)相比,无名信号量的生命周期通常受限于创建它的进程,并且它们不需要一个与系统相关的名字。
以下是关于POSIX无名信号量的详细介绍:
- 值:信号量有一个关联的整数值,最初可以设为任意非负整数。
- P操作/等待:当线程尝试执行P操作(或称为等待/下降/获取操作)时,如果信号量的值大于0,则它将减少该值并继续。如果值是0,线程将阻塞,直到值变得非零。
- V操作/发出:线程执行V操作(或称为发出/增加/释放操作)将增加信号量的值。如果有线程正在因P操作而被阻塞,它们中的一个将被唤醒。
主要函数
-
初始化:
sem_init
int sem_init(sem_t *sem, int pshared, unsigned int value);
sem
: 指向要初始化的信号量的指针。pshared
: 如果为非零值,则信号量在进程间共享。如果为0,则只在进程的线程之间共享。value
: 信号量的初始值。
-
销毁:
sem_destroy
int sem_destroy(sem_t *sem);
- 销毁之前使用
sem_init
初始化的信号量。
- 销毁之前使用
-
等待:
sem_wait
int sem_wait(sem_t *sem);
- 减少信号量的值。如果值已经是0,那么调用线程将被阻塞,直到信号量的值变为正数。
-
尝试等待:
sem_trywait
int sem_trywait(sem_t *sem);
- 尝试减少信号量的值。如果值为0,该函数立即返回,不会阻塞。
-
发出:
sem_post
int sem_post(sem_t *sem);
- 增加信号量的值。如果有线程因
sem_wait
而被阻塞,其中一个将被唤醒。
- 增加信号量的值。如果有线程因
使用注意事项
-
无名信号量主要用于线程间的同步。尽管
pshared
参数可以使它们在进程之间共享,但通常在这种情况下使用命名信号量更为合适。 -
当不再需要信号量时,应使用
sem_destroy
销毁它,以释放相关的资源。 -
和所有同步原语一样,使用信号量时需要注意避免死锁、活锁和竞争条件。
无名信号量在多线程应用中非常有用,因为它们为线程提供了一种简单且有效的同步机制,特别是当需要对资源访问进行互斥或需要线程之间的协作时。
示例
我们来看一个简单的无名信号量的例子。这个例子中,有两个线程:一个生产者线程和一个消费者线程。生产者生成一个整数并将其存储在共享变量中,消费者读取这个整数。这两个线程通过无名信号量来同步,以确保生产者在消费者读取整数之前生产整数,消费者在生产者生产新的整数之前读取整数。
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
sem_t sem_prod, sem_cons;
int shared_var;
void *producer(void *arg) {
for (int i = 1; i <= 5; i++) {
sem_wait(&sem_prod); // Wait until the consumer has consumed the last item
shared_var = i; // Produce an item
printf("Produced: %d\n", shared_var);
sem_post(&sem_cons); // Signal the consumer that an item has been produced
}
return NULL;
}
void *consumer(void *arg) {
for (int i = 1; i <= 5; i++) {
sem_wait(&sem_cons); // Wait until the producer has produced an item
printf("Consumed: %d\n", shared_var);
sem_post(&sem_prod); // Signal the producer that the item has been consumed
}
return NULL;
}
int main() {
pthread_t prod_tid, cons_tid;
sem_init(&sem_prod, 0, 1); // Initialize the producer semaphore to 1
sem_init(&sem_cons, 0, 0); // Initialize the consumer semaphore to 0
pthread_create(&prod_tid, NULL, producer, NULL); // Create producer thread
pthread_create(&cons_tid, NULL, consumer, NULL); // Create consumer thread
pthread_join(prod_tid, NULL); // Wait for producer thread to finish
pthread_join(cons_tid, NULL); // Wait for consumer thread to finish
sem_destroy(&sem_prod); // Destroy the producer semaphore
sem_destroy(&sem_cons); // Destroy the consumer semaphore
return 0;
}
执行结果如下:
majn@tiger:~/C_Project/prod_cons$ ./prod_cons
Produced: 1
Consumed: 1
Produced: 2
Consumed: 2
Produced: 3
Consumed: 3
Produced: 4
Consumed: 4
Produced: 5
Consumed: 5
在这个例子中:
sem_prod
和sem_cons
是无名信号量,用于生产者和消费者之间的同步。sem_prod
初始化为1,允许生产者线程首先运行。sem_cons
初始化为0,因为一开始没有可供消费的项目。- 生产者线程生成一个整数并将其存储在
shared_var
中,然后通过sem_post(&sem_cons)
信号消费者可以消费。 - 消费者线程等待
sem_cons
信号量,读取shared_var
中的整数,然后通过sem_post(&sem_prod)
信号生产者可以生产。 - 主函数等待这两个线程完成,并最后销毁信号量。
【注】在上述示例中,pthread_t tid;
是一个变量,用于存储新创建的线程的线程ID。此变量在声明时确实没有初始化,但这不是问题,因为它将在 pthread_create
调用中被明确地设置。
当调用 pthread_create
时,其第一个参数是一个指向 pthread_t
类型变量的指针。这个变量用于存储新创建的线程的ID。pthread_create
在成功地创建线程后将会设置它。
pthread_create & pthread_join
pthread_create
和 pthread_join
是 POSIX 线程(通常称为 pthreads)编程中的两个基本函数。它们分别用于创建新的线程和等待线程完成。以下是对这两个函数的详细说明:
1. pthread_create
此函数用于创建一个新的线程。
原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
参数:
thread
: 指向pthread_t
类型变量的指针,该变量用于存储新线程的ID。attr
: 指向pthread_attr_t
的指针,用于设置线程属性。如果设置为NULL
,则使用默认属性。start_routine
: 新线程启动时要执行的函数。这个函数必须返回void *
并接受一个void *
参数。arg
: 传递给start_routine
的参数。
返回值:
0
: 成功。- 错误号: 失败。
2. pthread_join
此函数用于等待线程完成。调用线程会被阻塞,直到指定的线程完成为止。
原型:
int pthread_join(pthread_t thread, void **retval);
参数:
thread
: 要等待的线程的ID。retval
: 一个指向void *
的指针,用于捕获线程的返回值。如果不关心返回值,可以设置为NULL
。
返回值:
0
: 成功。- 错误号: 失败。
示例
以下是一个简单的示例,说明如何使用 pthread_create
和 pthread_join
:
#include <stdio.h>
#include <pthread.h>
void *print_hello(void *data) {
char *message = (char *)data;
printf("%s\n", message);
return NULL;
}
int main() {
pthread_t tid;
char *message = "Hello from the thread!";
// 创建一个新线程
if (pthread_create(&tid, NULL, print_hello, message) != 0) {
printf("Error creating thread.\n");
return 1;
}
// 等待线程完成
pthread_join(tid, NULL);
printf("Thread has finished execution.\n");
return 0;
}
在上述示例中,主函数创建了一个新的线程,该线程调用 print_hello
函数并传递一个消息作为参数。然后,主函数使用 pthread_join
等待线程完成执行。
majn@tiger:~/C_Project/pthread_project$ ./pthread_demo
Hello from the thread!
Thread has finished execution.