C 中的线程:生产者消费者永远 运行
Threading In C: Producer Consumer taking forever to run
我是线程概念的新手。
我在 C 中处理生产者消费者问题,但与生产者并行时,消费者线程没有 运行。
我的代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
int S;
int E;
int F;
void waitS(){
//printf("hbasd");
while(S<=0);
S--;
}
void signalS(){
S++;
}
void waitE(){
while(E<=0);
E--;
}
void signalE(){
E++;
}
void waitF(){
while(F<=0);
F--;
}
void signalF(){
F++;
}
int p,c;
void* producer(void *n){
int *j = (int *)n;
int i = *j;
while(1){
waitS();
waitE();
printf("Producer %d\n",E);
signalS();
signalF();
p++;
if(p>=i){
printf("Exiting: producer\n");
pthread_exit(0);
}
}
}
void* consumer(void *n){
int *j = (int *)n;
int i = *j;
while(1){
waitS();
waitF();
printf("Consumer %d\n",E);
signalS();
signalE();
c++;
if(c>=i){
printf("Exiting Consumer\n");
pthread_exit(0);
}
}
}
int main(int argc, char* argv[]){
int n = atoi(argv[1]);
E = n;
S = 1;
F = 0;
int pro = atoi(argv[2]);
int con = atoi(argv[3]);
pthread_t pid, cid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_create(&pid,&attr,producer,(void *)&pro);
pthread_create(&cid,&attr,consumer,(void *)&con);
pthread_join(pid,NULL);
pthread_join(cid,NULL);
}
当我将输入作为 ./a.out 3 4 3
即 n=3, pro = 4, con = 3
我只是遇到了一种死锁的情况。
我希望输出像
制作人 2
生产者 1
生产者 0
消费者 0
消费者 1
生产者 0
退出:制作人
消费者 0
退出:消费者
...类似的输出,其中生产者 运行s 4 次,消费者三次
当我输入 ./a.out 4 4 3
我得到以下输出
制片人 3
生产者 2
生产者 1
生产者 0
退出:制作人
消费者 0
消费者 1
消费者 2
退出:消费者
从结果中我得出结论,pthread 生产者首先执行,然后是 pthread 消费者。
我希望它们同时执行,以便在给出 3 4 3 等测试用例时得到类似于第一个预期输出的答案。
您正在从不同的线程访问非原子变量,而没有任何类型的同步;这是一个竞争条件,它会导致未定义的行为。
特别是,现代 CPU 为每个 CPU 核心提供单独的寄存器和单独的缓存,这意味着如果线程 运行 在 CPU 核心 #1修改变量的值,该修改可能仅在 CPU #1 的缓存中保留一段时间,而不会 "pushed out" 进入 RAM,因此 [=21= 上的另一个线程 运行 ] 核心 #2 可能不会 "see" 线程 #1 的更新很长一段时间(或者可能永远不会)。
处理此问题的传统方法是使用一个或多个互斥锁序列化对共享变量的访问(参见 pthread_mutex_init()
、pthread_mutex_lock()
、pthread_mutex_unlock()
等),或使用原子变量而不是标准整数来表示要同时从多个线程访问的值。这两种机制都有保障措施以确保不会发生未定义的行为(如果您正确使用它们)。
你不能在没有同步的情况下从两个不同的线程访问相同的内存。 pthreads 的标准非常清楚地说明了这一点 here:
Applications shall ensure that access to any memory location by more than one thread of control (threads or processes) is restricted such that no thread of control can read or modify a memory location while another thread of control may be modifying it. Such access is restricted using functions that synchronize thread execution and also synchronize memory with respect to other threads.
此外,即使我们忽略许多 CPUs 不同步内存,除非你明确要求他们这样做,你的代码在普通 C 中仍然是不正确的,因为如果变量可以在你背后改变,他们应该不稳定。但是,尽管 volatile 可能对某些 CPU 有帮助,但它对 pthreads 是不正确的。
只需使用适当的锁定,不要旋转全局变量,有一些方法可以比使用 CPU 便宜得多的房间供暖。
一般来说,你应该使用同步原语,但与其他回答者不同,我相信如果我们运行这个程序在x86架构和防止编译器优化代码中的一些关键部分。
According to Wikipedia,x86架构几乎顺序一致,实现一个生产者-消费者算法绰绰有余
成功实现这种生产者-消费者算法的规则非常简单:
- 我们必须避免从不同的线程写入同一个变量,即如果一个线程写入变量
X
,另一个线程只从 X
读取
- 我们必须明确地告诉编译器我们的变量可能会在某处发生变化,即在线程变量之间共享的所有变量上使用
volatile
关键字。
这是基于您的代码的工作示例。生产者生产从 5 到 0 的数字,消费者消费它们。请记住,由于在其他架构上的顺序较弱,这仅适用于 x86:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
volatile int P = 0;
volatile int C = 0;
volatile int value = 0;
void produce(int v)
{
value = v;
P++;
}
int consume()
{
int v = value;
C++;
return v;
}
void waitForConsumer()
{
while (C != P)
;
}
void waitForProducer()
{
while (C == P)
;
}
void *producer(void *n)
{
int i = *(int *)n;
while (1) {
waitForConsumer();
printf("Producing %d\n", i);
produce(i);
i--;
if (i < 0) {
printf("Exiting: producer\n");
pthread_exit(0);
}
}
}
void *consumer(void *n)
{
while (1) {
waitForProducer();
int v = consume();
printf("Consumed %d\n", v);
if (v == 0) {
printf("Exiting: consumer\n");
pthread_exit(0);
}
}
}
int main(int argc, char *argv[])
{
int pro = 5;
pthread_t pid, cid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_create(&pid, &attr, producer, (void *)&pro);
pthread_create(&cid, &attr, consumer, NULL);
pthread_join(pid, NULL);
pthread_join(cid, NULL);
}
产生以下结果:
$ ./a.out
Producing 5
Producing 4
Consumed 5
Consumed 4
Producing 3
Producing 2
Consumed 3
Consumed 2
Producing 1
Producing 0
Exiting: producer
Consumed 1
Consumed 0
Exiting: consumer
有关更多信息,我真的推荐 Herb Sutter 的演讲 atomic<> Weapons,它很长,但包含您需要了解的有关排序和原子的所有信息。
尽管上面列出的代码在 x86 上可以正常工作,但我真的鼓励您观看上面的演示并使用内置原子,例如 __atomic_load_n()
,它将在任何平台上生成正确的汇编代码。
为生产者和消费者各自创建新线程,即所有生产者和消费者都有自己的线程。
我是线程概念的新手。 我在 C 中处理生产者消费者问题,但与生产者并行时,消费者线程没有 运行。
我的代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
int S;
int E;
int F;
void waitS(){
//printf("hbasd");
while(S<=0);
S--;
}
void signalS(){
S++;
}
void waitE(){
while(E<=0);
E--;
}
void signalE(){
E++;
}
void waitF(){
while(F<=0);
F--;
}
void signalF(){
F++;
}
int p,c;
void* producer(void *n){
int *j = (int *)n;
int i = *j;
while(1){
waitS();
waitE();
printf("Producer %d\n",E);
signalS();
signalF();
p++;
if(p>=i){
printf("Exiting: producer\n");
pthread_exit(0);
}
}
}
void* consumer(void *n){
int *j = (int *)n;
int i = *j;
while(1){
waitS();
waitF();
printf("Consumer %d\n",E);
signalS();
signalE();
c++;
if(c>=i){
printf("Exiting Consumer\n");
pthread_exit(0);
}
}
}
int main(int argc, char* argv[]){
int n = atoi(argv[1]);
E = n;
S = 1;
F = 0;
int pro = atoi(argv[2]);
int con = atoi(argv[3]);
pthread_t pid, cid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_create(&pid,&attr,producer,(void *)&pro);
pthread_create(&cid,&attr,consumer,(void *)&con);
pthread_join(pid,NULL);
pthread_join(cid,NULL);
}
当我将输入作为 ./a.out 3 4 3 即 n=3, pro = 4, con = 3
我只是遇到了一种死锁的情况。
我希望输出像
制作人 2 生产者 1 生产者 0 消费者 0 消费者 1 生产者 0 退出:制作人 消费者 0 退出:消费者
...类似的输出,其中生产者 运行s 4 次,消费者三次
当我输入 ./a.out 4 4 3 我得到以下输出
制片人 3 生产者 2 生产者 1 生产者 0 退出:制作人 消费者 0 消费者 1 消费者 2 退出:消费者
从结果中我得出结论,pthread 生产者首先执行,然后是 pthread 消费者。
我希望它们同时执行,以便在给出 3 4 3 等测试用例时得到类似于第一个预期输出的答案。
您正在从不同的线程访问非原子变量,而没有任何类型的同步;这是一个竞争条件,它会导致未定义的行为。
特别是,现代 CPU 为每个 CPU 核心提供单独的寄存器和单独的缓存,这意味着如果线程 运行 在 CPU 核心 #1修改变量的值,该修改可能仅在 CPU #1 的缓存中保留一段时间,而不会 "pushed out" 进入 RAM,因此 [=21= 上的另一个线程 运行 ] 核心 #2 可能不会 "see" 线程 #1 的更新很长一段时间(或者可能永远不会)。
处理此问题的传统方法是使用一个或多个互斥锁序列化对共享变量的访问(参见 pthread_mutex_init()
、pthread_mutex_lock()
、pthread_mutex_unlock()
等),或使用原子变量而不是标准整数来表示要同时从多个线程访问的值。这两种机制都有保障措施以确保不会发生未定义的行为(如果您正确使用它们)。
你不能在没有同步的情况下从两个不同的线程访问相同的内存。 pthreads 的标准非常清楚地说明了这一点 here:
Applications shall ensure that access to any memory location by more than one thread of control (threads or processes) is restricted such that no thread of control can read or modify a memory location while another thread of control may be modifying it. Such access is restricted using functions that synchronize thread execution and also synchronize memory with respect to other threads.
此外,即使我们忽略许多 CPUs 不同步内存,除非你明确要求他们这样做,你的代码在普通 C 中仍然是不正确的,因为如果变量可以在你背后改变,他们应该不稳定。但是,尽管 volatile 可能对某些 CPU 有帮助,但它对 pthreads 是不正确的。
只需使用适当的锁定,不要旋转全局变量,有一些方法可以比使用 CPU 便宜得多的房间供暖。
一般来说,你应该使用同步原语,但与其他回答者不同,我相信如果我们运行这个程序在x86架构和防止编译器优化代码中的一些关键部分。
According to Wikipedia,x86架构几乎顺序一致,实现一个生产者-消费者算法绰绰有余
成功实现这种生产者-消费者算法的规则非常简单:
- 我们必须避免从不同的线程写入同一个变量,即如果一个线程写入变量
X
,另一个线程只从X
读取
- 我们必须明确地告诉编译器我们的变量可能会在某处发生变化,即在线程变量之间共享的所有变量上使用
volatile
关键字。
这是基于您的代码的工作示例。生产者生产从 5 到 0 的数字,消费者消费它们。请记住,由于在其他架构上的顺序较弱,这仅适用于 x86:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
volatile int P = 0;
volatile int C = 0;
volatile int value = 0;
void produce(int v)
{
value = v;
P++;
}
int consume()
{
int v = value;
C++;
return v;
}
void waitForConsumer()
{
while (C != P)
;
}
void waitForProducer()
{
while (C == P)
;
}
void *producer(void *n)
{
int i = *(int *)n;
while (1) {
waitForConsumer();
printf("Producing %d\n", i);
produce(i);
i--;
if (i < 0) {
printf("Exiting: producer\n");
pthread_exit(0);
}
}
}
void *consumer(void *n)
{
while (1) {
waitForProducer();
int v = consume();
printf("Consumed %d\n", v);
if (v == 0) {
printf("Exiting: consumer\n");
pthread_exit(0);
}
}
}
int main(int argc, char *argv[])
{
int pro = 5;
pthread_t pid, cid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_create(&pid, &attr, producer, (void *)&pro);
pthread_create(&cid, &attr, consumer, NULL);
pthread_join(pid, NULL);
pthread_join(cid, NULL);
}
产生以下结果:
$ ./a.out
Producing 5
Producing 4
Consumed 5
Consumed 4
Producing 3
Producing 2
Consumed 3
Consumed 2
Producing 1
Producing 0
Exiting: producer
Consumed 1
Consumed 0
Exiting: consumer
有关更多信息,我真的推荐 Herb Sutter 的演讲 atomic<> Weapons,它很长,但包含您需要了解的有关排序和原子的所有信息。
尽管上面列出的代码在 x86 上可以正常工作,但我真的鼓励您观看上面的演示并使用内置原子,例如 __atomic_load_n()
,它将在任何平台上生成正确的汇编代码。
为生产者和消费者各自创建新线程,即所有生产者和消费者都有自己的线程。