C中的线程:生产者-消费者永远需要运行
我不熟悉线程的概念。 我在C中处理生产者-消费者问题,但消费者线程在与生产者并行时不运行 我的代码如下:C中的线程:生产者-消费者永远需要运行,c,multithreading,pthreads,producer-consumer,C,Multithreading,Pthreads,Producer Consumer,我不熟悉线程的概念。 我在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++; } vo
#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);
}
#包括
#包括
#包括
int-S;
INTE;
int F;
void waitS(){
//printf(“hbasd”);
而(S您正在从不同的线程访问非原子变量,而没有任何类型的同步;这是一种竞争条件,它会导致未定义的行为
特别是,现代CPU为每个CPU核心提供单独的寄存器和单独的缓存,这意味着如果在CPU核心#1上运行的线程修改变量的值,该修改可能会在CPU核心#1的缓存中单独保留一段时间,而不会“推出”到RAM,因此在CPU核心#2上运行的另一个线程可能不会“看到”线程#1的更新时间很长(也许永远不会)
处理此问题的传统方法是使用一个或多个互斥体序列化对共享变量的访问(请参见pthread\u mutex\u init()
,pthread\u mutex\u lock()
,pthread\u mutex\u unlock()
,等等),或对要同时从多个线程访问的值使用原子变量而不是标准整数。这两种机制都有保护措施,以确保不会发生未定义的行为(如果您正确使用它们).如果没有同步,您无法从两个不同的线程访问相同的内存。pthreads的标准非常清楚地说明了这一点:
应用程序应确保通过一个以上的控制线程(线程或进程)访问任何内存位置受到限制,因此当另一个控制线程可能正在修改内存位置时,任何控制线程都无法读取或修改该内存位置。使用同步线程执行以及与其他线程同步内存的函数来限制这种访问
此外,即使我们忽略了许多CPU不同步内存,除非您明确要求它们同步内存,但在普通C语言中,您的代码仍然是不正确的,因为如果变量可以在背后更改,它们应该是易失性的。但是,即使易失性在某些CPU上可能有所帮助,但对于pthreads来说,它是不正确的
只要使用适当的锁定,不要旋转全局变量,有一些方法加热房间比使用CPU便宜得多。一般来说,您应该使用同步原语,但与其他回答不同,我确实相信,如果我们在x86体系结构上运行此程序,并且防止编译器运行优化代码中的一些关键部分
,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);
}
要了解更多信息,我真的推荐赫伯·萨特(Herb Sutter)的演讲,该演讲很长,但有关于排序和原子学的所有知识
尽管上面列出的代码在x86上可以正常工作,但我真的鼓励您观看上面的演示并使用内置原子,如\uuuuuu atomic\u load\n()
,它将在任何平台上生成正确的汇编代码。为生产者和消费者各自创建新线程,即所有生产者和消费者都有自己的线程。这里的很多东西可能会搞糟,例如,全局
变量S
是在不从生产者和消费者进行任何同步的情况下访问的de>和使用者线程。即使访问了S,waitS()也会负责同步waitS()的目标是什么
?这是一个sephamore信号量
比简单的while
循环要复杂得多。正如其中一个答案所解释的,您必须使用互斥锁
或原子变量
或信号量
对这些变量进行读/写操作。不会等待()注意同步部分??顺便说一句,互斥不会使生产者先运行,消费者后运行??这不应该发生没有帮助,因为编译器不知道变量S可能会在任何时候被另一个线程修改,因此可能会将S值的本地副本缓存在本地寄存器或特定于核心的缓存中;因此,即使另一个核心确实更改了S的值,运行的等待()调用可能看不到更改。互斥锁一次只允许一个线程“持有”互斥锁——即,如果线程A已锁定互斥锁,线程B调用pthread_mutex_lock(),那么线程B的pthread_mutex_lock()调用将不会返回,直到线程A调用pthread_mutex_unlock()为止。在该限制之外,两个线程可以同时运行。通常情况下,这不是问题,因为您尝试确保每个线程持有互斥锁的时间尽可能短,但如果它困扰您,您可以避免使用互斥锁,而改为使用原子变量。
$ ./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