【问题标题】:Problem in implementing multiple threading for operations on a linked list为链表上的操作实现多线程的问题
【发布时间】:2020-03-09 14:21:11
【问题描述】:

在以下代码中,我尝试使用多个线程处理各种链表操作。可以支持多个链表,并且所有函数都是通用的,除了我保留了一些 printf 语句来调试代码。

typedef void (*gen_fun_t)(void *);

ll_t thread[2];
int ret;

#define RWLOCK(lt, lk) (ret=(lt) == l_read)? pthread_rwlock_rdlock(&(lk)): pthread_rwlock_wrlock(&(lk))
#define RWUNLOCK(lk) pthread_rwlock_unlock(&(lk));

typedef enum locktype locktype_t;

enum locktype
{
    l_read,
    l_write
};

struct node
{
    void *val;

    struct node  *next;

    pthread_rwlock_t m;
};


struct ll
{
        int len;

        struct node *head;
        pthread_rwlock_t m;

        gen_fun_t val_teardown;

        gen_fun_t val_printer;
};
typedef struct ll* ll_t;

typedef struct node * ll_node;

ll_t create( gen_fun_t val_teardown)
{
        ll_t list=(ll_t)malloc(sizeof(struct ll));
        list->head=NULL;
        list->len=0;
        list->val_teardown = val_teardown;

        pthread_rwlock_init(&list->m, NULL);

        return list;
}

ll_node new_node(void *val)
{
        ll_node node= (ll_node)malloc(sizeof(struct node));
        node->val=val;
        node->next=NULL;
        pthread_rwlock_init(&node->m, NULL);

        return node;
}


int remove_mid(ll_t list, int n)
{
        printf("Before deletion \n");
        print(list);
        ll_node temp;
        if(n==0)
        {
                temp=list->head;
                list->head=temp->next;
                printf("%d \n", *(int *)(list->head)->val);
        }
        else
        {
                ll_node it;
                it=list->head;
                for(;n>1;n--)
                        it=it->next;
                printf("%d \n", *(int *)(it->next)->val);
                temp=it->next;
                it->next=it->next==NULL? NULL: temp->next;
                printf("%d \n", *(int *)(it->next)->val);
        }
        (list->len)--;

        list->val_teardown(temp->val);
        free(temp);
        return list->len;
}

int insert_mid(ll_t list, void *val, int n)
{
        ll_node node= new_node(val);

        if(n==0)
        {
                node->next=list->head;
                list->head=node;
        }
        else
        {
                ll_node it;
                it=list->head;
                for(;n>1;n--)
                        it=it->next;
                node->next=it->next;
                it->next=node;
                printf("After insertion \n");
                print(list);
                printf("\n");
        }
        (list->len)++;
        return list->len;
}

void *thread_operation(void * arg)
{
        long id= (long) arg;

        if(id==0)
        {
                        RWLOCK(l_write, list[0]->m);
                        //RWLOCK(l_read, list[0]->m);
                        printf("The status of lock operation is %d \n", ret);
                        printf("\nThread: %ld \n", id);
                        int v=rand()%100;
                        int pos=rand()%list[0]->len;
                        printf("The position inserted is %d \n",pos+1);
                        pos=insert_mid(list[0], &v, pos);
                        //RWUNLOCK(list[0]->m);
                        RWUNLOCK(list[0]->m);

        }
        else
        {
                        RWLOCK(l_write, list[0]->m);
                        //RWLOCK(l_read, list[0]->m);
                        printf("The status of lock operation is %d \n", ret);
                        printf("\nThread: %ld \n", id);
                        int pos=rand()%list[0]->len;
                        printf("The position to be deleted is %d \n", pos+1);
                        pos=remove_mid(list[0], pos);
                        print(list[0]);
                        //RWUNLOCK(list[0]->m);
                        RWUNLOCK(list[0]->m);
        }
}

int main()
{

        int thread_count=2;
        long thread;
        srand(time(0));

        list[0]= create(int_tear);
        list[0]->val_printer = int_printer;

        list[1]=create(float_tear);
        list[1]->val_printer= float_printer;

        pthread_t *thread_handles;

        int l, a=2, b=8, c=15;

        l=insert_first(list[0], &a);
        l=insert_end(list[0], &b);
        l=insert_mid(list[0], &c, rand()%l);

        double start, finish, elapsed;

        start=clock();

        thread_handles= (pthread_t *) malloc(thread_count*sizeof(pthread_t));

        for(thread=0;thread<thread_count;thread++)
                pthread_create(&thread_handles[thread], NULL, thread_operation, (void *)thread);

        for(thread=0;thread<thread_count;thread++)
                pthread_join(thread_handles[thread], NULL);

        finish=clock();
        elapsed =(finish-start)/CLOCKS_PER_SEC;
        return 0;
}

输出为

Thread: 0 
The position to be inserted is 3 
After insertion 
ll: 2 15 79 8 


Thread: 1 
The position to be deleted is 1 
Before deletion 
ll: 2 15 -2087655584 8

ll: 15 -2087655584 8 

显然,insert_mid 函数将 79 插入到位置 2,但为什么它会变为 -2087655584?解决办法是什么?

如果需要任何信息,请告知。

【问题讨论】:

  • 您没有将源发布到 create()。当你这样做的时候,为什么不添加一些代码来检查 pthread_rwlock_{rdlock,wrlock,unlock}() 的返回值。尝试想象如果这些线程返回错误而不是暂停执行,您的线程会做什么。此外,对同一个操作进行写锁和读锁是没有意义的;为修改而写,为查看而阅读。 (这就是为什么我怀疑你没有在 create 中执行 pthread_rwlock_init(),所以这些函数是无操作的)。
  • 我不知道这是否与你的问题有关,但你不应该同时获得读锁和写锁。如果您只想读取数据,则获得读锁,如果您想写入,则获得写锁,然后解锁一次。检查返回值是否成功获取锁。如果你不持有锁,解锁的行为是不确定的。
  • 请显示new_node函数。很确定你保留了一个指向 v 的指针,它分配在你创建的第一个线程的堆栈上。
  • @Bodo 是的,你是对的。这里只需要 write_lock。我只是想调试,所以我想为什么不暂停列表上的所有操作,因为显然读取操作失败(读取错误值)
  • @mevets 我在编辑中包含了 create() 函数。我以前做过 pthread_rwlock_init() 。获取锁的返回值为0,表示一切正常。

标签: c multithreading linked-list critical-section readwritelock


【解决方案1】:

简短的总结:你将一个局部变量的地址存储在一个列表节点中,这个变量超出了范围,所以地址变得无效。


您的函数new_node(void *val) 获取一个地址作为参数,并将该地址作为node-&gt;val 存储在节点结构中。

在您的 main 函数中,您首先使用局部变量的地址创建 3 个节点。

        int l, a=2, b=8, c=15;

        l=insert_first(list[0], &a);
        l=insert_end(list[0], &b);
        l=insert_mid(list[0], &c, rand()%l);

这些变量在main结束前都是有效的,所以这里没有问题。

在这个循环中

        for(thread=0;thread<thread_count;thread++)
                pthread_create(&thread_handles[thread], NULL, thread_operation, (void *)thread);

您创建运行thread_operation 的线程并传递循环计数器,并将其作为参数转换为void*

thread_operation 中,当使用参数值0 调用时,您使用局部变量v 的地址添加一个节点

void *thread_operation(void * arg)
{
        long id= (long) arg;

        if(id==0)
        {
/* ... */
                        int v=rand()%100;
                        int pos=rand()%list[0]->len;
/* ... */
                        pos=insert_mid(list[0], &v, pos); /* &v is the address of a local variable */
/* ... */
        }
        else
        {
/* ... */
        }
}

/* ... */
int insert_mid(ll_t list, void *val, int n)
{
        ll_node node= new_node(val); /* The address is passed to the new node. */
/* ... */
}

thread_operation离开身体时

        if(id==0)
        {
/* ... */
        }

变量超出范围并变为无效。 (如果你在没有优化的情况下编译你的程序,它可能会保持它的值直到函数返回,但这不能保证。)

thread_operation返回时,这个变量所在的栈区被释放,以后会被其他函数调用使用。

当您打印列表的内容时,ID 为 0 的线程插入的节点指向堆栈上的某个地址,该地址曾经保存变量 v,并且可能正在使用中,或者之后可能已用于其他用途.这就是价值被改变的原因。这是未定义的行为。

要解决此问题,您可以将节点结构更改为将变量的值存储int而不是将变量的地址作为@ 987654337@ 或者你必须像这样分配内存并创建数据的副本

ll_node new_node(void *val, size_t size)
{
        ll_node node= (ll_node)malloc(sizeof(struct node));
        /* TODO check that malloc did not return NULL */
        node->val = malloc(size);
        /* TODO check that malloc did not return NULL */
        memcpy(node->val, val, size);
        node->next=NULL;
        pthread_rwlock_init(&node->m, NULL);

        return node;
}

其他可能的问题:

您在每个列表节点中创建一个锁定对象。如果要使用它来保护对节点中数据的访问,则相应的线程必须(至少)获得列表的读锁,以防止其他线程删除该节点。

【讨论】:

  • 感谢您的解释。
猜你喜欢
  • 2011-07-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-02
  • 2020-02-25
相关资源
最近更新 更多