内存同步

时间:2016-01-08 19:45:33

标签: c multithreading pthreads

我有函数display.c:

/* DO NOT EDIT THIS FILE!!!  */

#include <stdio.h>
#include <unistd.h>
#include "display.h"

void display(char *str)
{
    char *p;

    for (p=str; *p; p++)
    {
        write(1, p, 1);
        usleep(100);
    }
}

和display.h是:

/* DO NOT EDIT THIS FILE!!!  */

#ifndef __CEID_OS_DISPLAY_H__
#define __CEID_OS_DISPLAY_H__
void display(char *);
#endif

我的任务是使用pthreads以获得以下输出:

abcd
abcd
abcd
..
..

请注意,我不能编辑文件display.c或文件display.c.我必须使用互斥锁才能成功完成上面显示的输出。

以下代码块是我最终尝试最终达到我想要的结果:

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <unistd.h>
#include <pthread.h>
#include "display.h"

pthread_t mythread1;
pthread_t mythread2;
pthread_mutex_t m1, m2;

void *ab(void *arg)
{
    pthread_mutex_lock(&m1);            
    display("ab");      
    pthread_mutex_unlock(&m1);
}    

void *cd(void *arg)
{    
    pthread_mutex_lock(&m1);       
    display("cd\n");            
    pthread_mutex_unlock(&m1);      
}

int main(int argc, char *argv[])
{
    pthread_mutex_init(&m1, NULL);
    pthread_mutex_init(&m2, NULL);  

    int i;

    for(i=0;i<10;i++)
    {     
        pthread_create(&mythread1, NULL, ab, NULL);         
        pthread_create(&mythread2, NULL, cd, NULL);    
    }

    pthread_join(mythread1, NULL);
    pthread_join(mythread2, NULL);   
    pthread_mutex_destroy(&m1);
    pthread_mutex_destroy(&m2);
    return EXIT_SUCCESS;    
}

上面代码的输出是这样的:

abcd
abcd
abcd
abcd
ababcd
cd
abcd
abcd
abcd
abcd

正如您所见,“ab”和“cd \ n”从不混合,但每次运行代码时输出都不同。我想确保每次运行此代码时输出都是:

abcd
abcd 
abcd

十次。

我真的很困惑,因为我找不到任何我已经知道的解决方案。

3 个答案:

答案 0 :(得分:2)

互斥锁不能(本身)解决您的问题。它可以防止你的两个线程同时运行,但它不能强制它们轮流。

除了互斥锁或一对信号量之外,您还可以使用条件变量。无论哪种方式,关键是要始终保持对哪个线程转变的感觉。

我自己,我认为信号量方法更容易理解和编码。每个信号量主要与不同的线程相关联。该线程必须锁定信号量才能继续。当它完成一次迭代时,它解锁另一个信号量以允许另一个线程继续,并循环返回以尝试再次锁定它自己的信号量(它还不能做)。另一个线程以相同的方式工作,但信号量角色反转。粗略地说,那将是:

sem_t sem1;
sem_t sem2;

// ...

void *thread1_do(void *arg) {
    int result;

    do {
        result = sem_wait(&sem1);
        // do something
        result = sem_post(&sem2);
    } while (!done);
}

void *thread2_do(void *arg) {
    int result;

    do {
        result = sem_wait(&sem2);
        // do something else
        result = sem_post(&sem1);
    } while (!done);
}

为简洁起见,省略了信号量初始化,错误检查等。

已更新以添加

由于您现在添加必须使用互斥锁(大概是以非平凡的方式),下一个最好的方法是引入一个条件变量(与互斥锁一起使用)和一个普通的共享变量来跟踪哪个线程就是这样。然后每个线程等待条件变量以获取互斥锁,在互斥锁的保护下检查共享变量以查看它是否轮到它,如果是,则继续。粗略地说,那将是:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int whose_turn = 1;

// ...

void *thread1_do(void *arg) {
    int result;

    result = pthread_mutex_lock(&mutex);
    while (1) {
        if (whose_turn == 1) {
            // do something
            whose_turn = 2;  // it is thread 2's turn next
        }
        // break from the loop if finished
        result = pthread_cond_broadcast(&cond);
        result = pthread_cond_wait(&cond, &mutex); 
    }
    result = pthread_mutex_unlock(&mutex);
}

void *thread1_do(void *arg) {
    int result;

    result = pthread_mutex_lock(&mutex);
    while (1) {
        if (whose_turn == 2) {
            // do something else
            whose_turn = 1;  // it is thread 1's turn next
        }
        // break from the loop if finished
        result = pthread_cond_broadcast(&cond);
        result = pthread_cond_wait(&cond, &mutex); 
    }
    result = pthread_mutex_unlock(&mutex);
}

为简洁起见,再次省略了错误检查。

特别注意,当线程等待条件变量时,它会释放相关的互斥锁。它在从等待返回之前获取互斥锁。另请注意,每次迭代都会检查是否轮到它继续。这是必要的,因为等待条件变量的虚假唤醒是可能的。

答案 1 :(得分:1)

您可以使用条件变量在线程之间轮流:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int turn = 0;

void *ab(void *arg)
{
    pthread_mutex_lock(&m1);
    while (turn != 0)
       pthread_cond_wait(&cond, &m1);

    display("ab");
    turn = 1;
    pthread_mutex_unlock(&m1);
    pthread_cond_signal(&cond);
}

void *cd(void *arg)
{
    pthread_mutex_lock(&m1);
    while (turn != 1)
       pthread_cond_wait(&cond, &m1);

    display("cd\n");
    turn = 0;
    pthread_mutex_unlock(&m1);
    pthread_cond_signal(&cond);
}

另一个问题是,你加入main()线程中创建的最后两对线程,这些线程不一定是最后执行的线程。如果早期创建的线程没有完成,那么当它正在使用并退出整个过程时,您正在销毁互斥锁​​m1

答案 2 :(得分:-1)

考虑这种问题的方法:

for(i=0;i<10;i++)
{     
    ab();
    cd();
}

根据显示的代码,这完全实现了您的目标。您的示例的问题在于您有效地阻止了任何同步,这甚至是您的目标!

假设在输出之前,你实际上想要做一些有用的事情来占用CPU时间,答案是你必须改变display()的代码,这根本不适合并行化。适当的并发代码被设计为独立于其他代码工作,特别是它不应该与其他调用竞争资源(锁),它不应该依赖于它完成的顺序。

总之,你不能从中学到很多,这是一个不好的例子。为了改进代码(您不想改变的代码,但这是您的问题),请考虑不同线程竞争的资源是stdout。如果他们各自写入自己的缓冲区,您可以创建线程,等待它们完成,然后才重新排序结果以便输出。