linux 多线程传参问题(使用phread_create() 传参问题)

这篇具有很好参考价值的文章主要介绍了linux 多线程传参问题(使用phread_create() 传参问题)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

linux 多线程传参

pthread_create() 函数在多线程传参时只能传值,不能传参数的地址,是因为多线程环境下,各个线程共享同一块内存空间。如果在一个线程中传递参数的地址给另一个线程,那么在多线程并发执行的情况下,可能会导致数据竞争和不确定的结果。

为了避免这种情况,pthread_create() 函数只允许传递参数的值,而不是地址。这样可以确保每个线程都有自己的参数副本,避免了数据竞争的问题。

如果需要在线程之间共享数据,可以使用全局变量或者互斥锁等同步机制来保证数据的一致性和安全性。

pthreat_create() 函数传地址 的错误示范

#include <iostream>
#include <pthread.h>
 
using namespace std;
 
#define NUM_THREADS 10
 
void* say_hello(void* args)
{
    int i = *((int*)args);//对传入的参数进行强制类型转换,由无类型指针变为整形数指针,然后再读取;
    cout << "hello in " << i << endl;
}
 
int main()
{
    pthread_t tids[NUM_THREADS];
    cout << "hello in main..." << endl;
    for(int i = 0; i < NUM_THREADS; ++i)
    {
        int ret = pthread_create(&tids[i], NULL, say_hello, (void *)&i);//传入的时候必须强制转换为void* 类型,即无类型指针
        cout << "Current pthread id =" << tids[i] << endl;//这里学会使用tids数组打印创建的进程id信息;
        if (ret != 0)
        {
           cout << "pthread_create error: error_code=" << ret << endl;
        }
    }
 
    pthread_exit(NULL);
}

代码结果

hello in main…
Current pthread id =140105057535552
hello in 1
Current pthread id =140105049142848
hello in 2
Current pthread id =140105040750144
hello in 3
Current pthread id =140105032357440
hello in 3
Current pthread id =140105023964736
hello in 4
Current pthread id =140105015572032
hello in 6
Current pthread id =140105007179328
hello in 7
Current pthread id =140104998786624
hello in 7
Current pthread id =140104990393920
hello in 9
Current pthread id =140104982001216
hello in 9

代码分析

这段代码的问题在于它试图将同一个变量 i 的地址作为参数传递给所有的线程。这可能会导致数据竞争,因为所有的线程都会尝试访问和修改同一个内存位置。

pthread_create() 被调用时,它会立即复制其参数(在这种情况下是 &i 的值)。然而,由于 i 是在 for 循环中定义的,所以在下一次迭代时,i 的值就会改变。这意味着,当线程开始执行 say_hello() 时,它看到的 i 的值可能与创建线程时传递的值不同。

解决这个问题的一种方法是为每个线程分配一个新的整数,并将其地址传递给 pthread_create()。这样,每个线程都有自己的参数副本。以下是修正后的代码:

改正方法一 为每个线程分配一个新的整数,并将其地址传递给 pthread_create()

#include <iostream>
#include <pthread.h>
 
using namespace std;
 
#define NUM_THREADS 10
 
void* say_hello(void* args)
{
    int i = *((int*)args);
    cout << "hello in " << i << endl;
    delete (int*)args; // 释放内存
}
 
int main()
{
    pthread_t tids[NUM_THREADS];
    cout << "hello in main..." << endl;
    for(int i = 0; i < NUM_THREADS; ++i)
    {
        int *p = new int(i); // 为每个线程分配一个新的整数
        int ret = pthread_create(&tids[i], NULL, say_hello, p);
        cout << "Current pthread id =" << tids[i] << endl;
        if (ret != 0)
        {
           cout << "pthread_create error: error_code=" << ret << endl;
        }
    }
 
    pthread_exit(NULL);
}

改正方法二 创建一个 indexes 数组来保存每个线程的索引值

流行的一种做法:把 i 赋值给一个数组保存,这样就避免了在同一地址取值。

#include <iostream>
#include <pthread.h>
 
using namespace std;
 
#define NUM_THREADS 10
 
void* say_hello(void* args)
{
    cout << "hello in thread " << *((int *)args) << endl;
}
 
int main()
{
    pthread_t tids[NUM_THREADS];
    int indexes[NUM_THREADS];//用个数组来保存i的值,就不会变了
 
    for(int i = 0; i < NUM_THREADS; ++i)
    {
        indexes[i] = i;//先保存i的值,在调用线程就不会出现问题了
        int ret = pthread_create( &tids[i], NULL, say_hello, (void *)&(indexes[i]) );
        cout << "Current pthread id =" << tids[i] << endl;
        if (ret != 0)
        {
           cout << "pthread_create error: error_code=" << ret << endl;
        }
    }
    for (int i = 0; i < NUM_THREADS; ++i)
        pthread_join(tids[i], NULL);
}

运行结果:

Current pthread id =hello in thread 0
140643635238464
Current pthread id =140643626845760
hello in thread 1
Current pthread id =140643618453056
hello in thread 2
Current pthread id =140643610060352
hello in thread 3
Current pthread id =140643601667648
hello in thread 4
Current pthread id =140643593274944
hello in thread 5
Current pthread id =140643584882240
hello in thread 6
Current pthread id =140643576489536
hello in thread 7
Current pthread id =140643568096832
hello in thread 8
Current pthread id =140643559704128
hello in thread 9

代码分析

这段代码通过创建一个 indexes 数组来保存每个线程的索引值,从而避免了之前代码中的问题。在创建线程时,它不再直接传递循环变量 i 的地址,而是传递 indexes[i] 的地址。这样,每个线程都会收到一个指向其自己索引值的指针,而不是一个可能在下一次循环迭代中被修改的 i 的地址。

此外,这段代码还添加了一个新的循环来等待所有线程完成。这是通过调用 pthread_join() 来实现的。这样可以确保主线程在所有子线程都完成之前不会退出,从而防止任何子线程在主线程已经退出后仍然尝试访问其参数。

总的来说,这段代码通过确保每个线程都有自己的参数副本,并正确地管理线程的生命周期,从而避免了数据竞争和其他并发问题。

在原始的代码中,所有线程都接收到了变量 i 的地址。由于 i 是在 for 循环中定义的,所以在每次迭代时,i 的值都会改变。这就意味着,当线程开始执行时,它看到的 i 的值可能与创建线程时传递的值不同。

而在修改后的代码中,我们创建了一个 indexes 数组,并将每个线程的索引值存储在数组的相应位置中。然后,我们将数组元素的地址(而不是 i 的地址)传递给每个线程。这样,即使 i 的值在每次迭代时都会改变,数组中的值也不会改变。因此,每个线程都会接收到一个指向其自己索引值的稳定指针。

总的来说,使用数组可以确保每个线程都有自己的参数副本,并且这些参数在整个线程的生命周期内都是稳定的。

改正方法三,使用传值替代传入参数的地址

#include <iostream>  
#include <pthread.h> 

using namespace std;  

#define NUM_THREADS 10 //线程数  

void* say_hello( void* args )  
{  
    cout << "hello in " << long(args) <<  endl;  
}   

int main()  
{  
    pthread_t tids[NUM_THREADS]; //线程id  
    cout << "hello in main.." << endl;  
    for( int i = 0; i < NUM_THREADS; ++i )  
    {  
        int ret = pthread_create( &tids[i], NULL, say_hello, (void*)i ); 
        //直接把i的值传过去  
        cout << "Current pthread id = " << tids[i] << endl; 
        //用tids数组打印创建的进程id信息  
        if( ret != 0 ) //创建线程成功返回0  
        {  
            cout << "pthread_create error:error_code=" << ret << endl;  
        }  
    } 
    for( int i = 0; i < NUM_THREADS; ++i )  
	{
         pthread_join(tids[i],NULL);
	}  
}  

代码结果

运行结果:

hello in main…

Current pthread id = 140406320125696

Current pthread id = 140406311732992

Current pthread id = 140406303340288

Current pthread id = 140406294947584

Current pthread id = 140406286554880

Current pthread id = 140406278162176

Current pthread id = 140406269769472

Current pthread id = 140406261376768

Current pthread id = 140406252984064

Current pthread id = 140406244591360

hello in 6

hello in 7

hello in 8

hello in 9

hello in 5

hello in 4

hello in 3

hello in 2

hello in 1

hello in 0


代码分析

这段代码中,每个线程的参数是整数 i 的值,而不是指向 i 的指针。这是通过将 i 强制转换为 void* 类型来实现的。然后,这个值被传递给 pthread_create(),并最终传递给 say_hello()。

在 say_hello() 中,参数被转换回 long 类型,并直接使用。因为这里传递的是值而不是地址,所以不需要在线程函数中释放内存,也就避免了 pthread_exit() 可能带来的问题。

此外,这段代码还添加了一个新的循环来等待所有线程完成。这是通过调用 pthread_join() 来实现的。这样可以确保主线程在所有子线程都完成之前不会退出,从而防止任何子线程在主线程已经退出后仍然尝试访问其参数。

总的来说,这段代码通过确保每个线程都有自己的参数副本,并正确地管理线程的生命周期,从而避免了数据竞争和其他并发问题。

更多例子

正确使用方法一:ptread_create()中传递值//使用值传递,而不是指针传递。

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

typedef struct {
    char* ch;
    int aa;
} STR;

void* func(void * arg) {
    int b = (int)arg;
    printf("%d\n", b);
    STR* str = (STR*)malloc(sizeof(STR));
    str->ch = "hello";
    str->aa = 100;
    pthread_exit((void*)str);    
}

int main() {
    pthread_t tid[2];
    int i;
    for(i=0; i<2; i++)
        pthread_create(&tid[i], NULL, func, (void *)i); //值传递,因为参数为地址,故强转成地址
    for(i=0; i<2; i++) { //回收每个子线程
        STR *re; //re位于主控线程的栈空间,但是re本身的值为子线程传给它的值。
        pthread_join(tid[i], (void **)&re);
        printf("Thread %d returned: %s %d\n", i, re->ch, re->aa);
        free(re);
    }
    return 0;
}

这段代码创建了两个线程,并且每个线程都返回一个STR结构体的指针。这个结构体包含一个字符串和一个整数。在主线程中,我们使用pthread_join()函数来获取每个子线程返回的结构体,并打印出其内容。

注意,我们需要在主线程中释放每个子线程返回的内存,以防止内存泄漏。在实际使用时,请确保正确处理所有可能的错误情况。例如,pthread_create()和malloc()函数可能会失败,并返回错误码或NULL指针。在这种情况下,应该检查返回值,并采取适当的错误处理措施。此外,这段代码假设所有子线程都会成功地返回一个STR结构体的指针。如果子线程可能返回NULL或其他类型的指针,那么需要在主线程中进行相应的检查。

这段代码中,pthread_create()函数的第四个参数是一个void*类型的指针,它指向需要传递给新线程的数据。在这个例子中,我们传递的是一个整数值,这个值是通过(void *)i进行类型转换得到的。每次调用pthread_create()时,我们都传递了一个不同的整数值,因此不会出现数据错乱的问题。

具体来说,当我们在循环中调用pthread_create()时,每次迭代都会创建一个新的线程,并传递一个与迭代变量i相对应的整数值。由于每个线程都接收到了一个唯一的整数值,因此它们不会互相干扰。

需要注意的是,如果我们试图直接传递迭代变量i的地址(例如(void *)&i),那么就可能会出现数据错乱的问题。这是因为所有线程都会共享同一个地址(即i的地址),而这个地址上的值在每次迭代时都会改变。因此,当线程开始运行时,它看到的值可能与创建线程时不同,这就导致了数据错乱。

总的来说,当使用pthread_create()函数创建线程并传递参数时,我们需要确保每个线程接收到的参数是唯一的,并且在其生命周期内都有效。这通常可以通过传值或动态分配内存来实现。在这个例子中,我们选择了传值的方式,因此不会出现数据错乱的问题。但是,在其他情况下(例如需要传递一个大型结构体或数组时),我们可能需要选择动态分配内存的方式。

正确使用方法二:ptread_create()中使用动态指针传递

//下例中,free与malloc不在同一函数中,这一点可以继续优化。



```c
void* func(void* arg)
{
    int value = *(int*)arg;
    free(arg);//------------------->在参数使用结束后,对malloc的内存释放。
    printf("%d\n",value);
}
int main(void)
{
    pthread_t pid[6];
    int ret;
    for (int i=0; i<6; ++i)
    {   
        int *p = malloc(sizeof(*p));
        if(p==NULL)
        {
            perror("malloc");
            exit(1);
        }
        *p = i;
        if ((ret=pthread_create(&pid[i],NULL,thread,(void*)p)) != 0)
        {   
            fprintf(stderr,"pthread_create:%s\n",strerror(ret));
            exit(1);
        }   
    } 
 
 
    for(int j=0;j<6;j++)
    {
        pthread_join(pid[j],NULL);
    }
}
```

请问这段代码为什么不会因为pthread_create()在多线程传参数中出错

这段代码中,pthread_create() 函数在创建新线程时,会将参数传递给新线程的启动函数。在这个例子中,每个新线程的启动函数是 func(),并且每个新线程都有自己的参数。

这段代码之所以不会出错,是因为它为每个新线程分配了一个新的内存空间来存储参数。这是通过 malloc() 函数实现的。然后,这个内存地址被传递给 pthread_create(),并最终传递给 func()

func() 中,参数被转换回 int* 类型,并通过解引用操作获取其值。然后,使用 free() 释放了之前通过 malloc() 分配的内存。

因此,每个线程都有自己的参数副本,不会出现多个线程尝试访问和修改同一内存位置的情况,从而避免了数据竞争和其他并发问题。这就是为什么这段代码能正确运行而不会出错的原因。希望这个解释对你有所帮助!

void* func(void* arg)
{
    int value = *(int*)arg;
    //free(arg);//------------------->在参数使用结束后,对malloc的内存释放。
    printf("%d\n",value);
    pthread_exit(arg);
}
int main(void)
{
    pthread_t pid[6];
    int ret;
    void *ptr;
    for (int i=0; i<6; ++i)
    {   
        int *p = malloc(sizeof(*p));
        if(p==NULL)
        {
            perror("malloc");
            exit(1);
        }
        *p = i;
        if ((ret=pthread_create(&pid[i],NULL,thread,(void*)p)) != 0)
        {   
            fprintf(stderr,"pthread_create:%s\n",strerror(ret));
            exit(1);
        }   
    } 
 
 
    for(int j=0;j<6;j++)
    {
        pthread_join(pid[j],&ptr);//-----------收到线程传过来的参数。
        free(ptr);//-----------------释放内存
        
    }
    exit(0);
}  

在第一段代码中,每个线程在使用完参数后立即释放了内存。这是通过在 func() 函数中调用 free(arg) 来实现的。

而在第二段代码中,内存的释放被推迟到了主线程中。每个子线程在执行完毕后,都会通过 pthread_exit(arg) 将参数返回给主线程。然后,在主线程中,pthread_join() 函数会获取这个参数,并将其存储在 ptr 中。最后,主线程在所有子线程都执行完毕后,释放了这些内存。

总的来说,第二段代码的优点是它允许主线程在子线程结束后获取并使用子线程的参数。然而,这也意味着主线程需要负责管理和释放这些内存,这可能会使代码变得更复杂。希望这个解释对你有所帮助!

错误方法三:错误示范,参数传递指针

//多个线程之间存在竞争,线程函数中 int value = (int)arg; 多个arg指针都指向了同一片内存,上述两种方法不存在竞争的原因是,一个指针指向一个变量。

void* func(void* arg)
{
    int value = *(int*)arg;
    printf("%d\n",value);
}
int main(void)
{
    pthread_t pid[6];
    int ret;
    for (int i=0; i<6; ++i)
    {   
        if ((ret=pthread_create(&pid[i],NULL,thread,(void*)&i)) != 0)
        {   
            fprintf(stderr,"pthread_create:%s\n",strerror(ret));
            exit(1);
        }   
    } 
}  

总结: 不能在线程创建过程中,改变传递的参数,避免该问题产生的方法是传递值或者使用动态申请内存的方法。

这段代码的主要问题在于它试图将同一个变量 i 的地址作为参数传递给所有的线程。这可能会导致数据竞争,因为所有的线程都会尝试访问和修改同一个内存位置。

pthread_create() 被调用时,它会立即复制其参数(在这种情况下是 &i 的值)。然而,由于 i 是在 for 循环中定义的,所以在下一次迭代时,i 的值就会改变。这意味着,当线程开始执行 func() 时,它看到的 i 的值可能与创建线程时传递的值不同。

此外,这段代码还存在另一个问题。在 main() 函数结束时,局部变量 i 将不再存在,但是如果仍有线程在运行并尝试访问 i,那么它们可能会看到无效的数据。

解决这个问题的一种方法是为每个线程分配一个新的整数,并将其地址传递给 pthread_create()。这样,每个线程都有自己的参数副本

使用chatgpt的结果

为什么pthread_create()能以数组或结构体作为参数传递,但是不会多线程并发导致数据不确定或数据竞争,请列举代码说明

在使用pthread_create()函数创建线程时,我们通常会将需要传递给新线程的参数打包到一个结构体中,然后传递这个结构体的地址12。这是因为pthread_create()函数的第四个参数是一个void*类型,也就是一个万能指针3。这个指针可以指向任何类型的数据,包括结构体。

然而,如果我们直接传递一个变量的地址(例如局部变量),可能会遇到问题。这是因为局部变量在函数返回后会被销毁,而新线程可能会在该函数返回后才开始运行。这样,新线程就可能访问到已经被销毁的内存,导致未定义的行为4。

因此,我们通常会选择以下两种方法来传递参数:

  1. 传值:直接将值作为void*类型传递。在新线程中,我们可以将这个void*类型的指针转换回原来的类型4。
  2. 动态分配内存:我们可以动态分配一块内存(例如使用malloc()函数),然后将需要传递的数据存储在这块内存中。这样,即使原函数返回,动态分配的内存仍然存在,新线程可以安全地访问这些数据4。

总的来说,pthread_create()函数在多线程传参时并不是只能传值,而是需要根据具体情况选择合适的方式来传递参数。如果选择不当,可能会导致访问已经被销毁的内存等问题4。

请分别用代码举例子

当然,以下是两种传递参数的方式的代码示例:

  1. 传值:
#include <pthread.h>
#include <stdio.h>

void* print_value(void* arg) {
    int value = (int)arg;
    printf("Value: %d\n", value);
    return NULL;
}

int main() {
    pthread_t thread_id;
    int value = 5;
    pthread_create(&thread_id, NULL, print_value, (void*)value);
    pthread_join(thread_id, NULL);
    return 0;
}

在这个例子中,我们直接将整数值作为void*类型传递给新线程。在新线程中,我们将这个void*类型的指针转换回int类型。

  1. 动态分配内存:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

void* print_value(void* arg) {
    int* ptr = (int*)arg;
    printf("Value: %d\n", *ptr);
    free(ptr);
    return NULL;
}

int main() {
    pthread_t thread_id;
    int* ptr = malloc(sizeof(int));
    *ptr = 5;
    pthread_create(&thread_id, NULL, print_value, ptr);
    pthread_join(thread_id, NULL);
    return 0;
}

在这个例子中,我们动态分配一块内存,并将需要传递的数据存储在这块内存中。这样,即使原函数返回,动态分配的内存仍然存在,新线程可以安全地访问这些数据。注意,在新线程中,我们需要使用free()函数来释放动态分配的内存,以防止内存泄漏。

请注意,以上代码仅供参考,并未进行错误处理。在实际使用时,请确保正确处理所有可能的错误情况。例如,pthread_create()malloc()函数可能会失败,并返回错误码或NULL指针。在这种情况下,应该检查返回值,并采取适当的错误处理措施。文章来源地址https://www.toymoban.com/news/detail-727514.html

到了这里,关于linux 多线程传参问题(使用phread_create() 传参问题)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux调用Selenium报session not created: Chrome failed to start: exited normally.的问题解决方式

    确定了浏览器版本和chromedriver版本可以对应得上,但是每次调用对应的服务都会报该错误 问题在于启动Selenium需要桌面,然而你的Linux上没有桌面,所以报错,解决这个问题的方式是将Selenium设置为不使用浏览器启动,然而不使用浏览器启动存在对应的元素没加载的问题,解决

    2024年02月03日
    浏览(96)
  • 【Linux】多线程02 --- 线程的同步互斥问题及生产消费模型

    🍎 作者: 阿润菜菜 📖 专栏: Linux系统编程 线程同步互斥问题是指多线程程序中,如何保证共享资源的正确访问和线程间的协作。 因为线程互斥是实现线程同步的基础和前提,我们先讲解线程互斥问题。 在多线程中,假设我们有一个黄牛抢票的代码,其中有一份共享资源

    2024年02月08日
    浏览(44)
  • Linux之 线程池 | 单例模式的线程安全问题 | 其他锁

    目录 一、线程池 1、线程池 2、线程池代码 3、线程池的应用场景 二、单例模式的线程安全问题 1、线程池的单例模式 2、线程安全问题 三、其他锁 线程池是一种线程使用模式。线程池里面可以维护一些线程。 为什么要有线程池? 因为在我们使用线程去处理各种任务的时候,

    2024年04月18日
    浏览(43)
  • linux线程池、基于线程池的单例模式、读者写者问题

    线程池: 一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。

    2024年02月03日
    浏览(37)
  • 【Linux】线程池设计/单例模式/STL、智能指针与线程安全/读者写者问题

    线程池:一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可

    2024年02月03日
    浏览(46)
  • 解决使用conda env create -f environment.yml安装依赖包时Installing pip dependencies过慢的问题

    问题描述 使用conda env create -f environment.yml安装依赖包时,会遇到Installing pip dependencies过慢的问题。这是由于没有使用镜像源。如下图所示 解决方案 可以尝试对environment.yml文件进行以下修改,添加镜像源即可: 将channels改为(注意要把default去掉): 并在pip的依赖包里添加上镜

    2024年02月12日
    浏览(54)
  • UNIX网络编程:socket & pthread_create()多线程 实现clients/server通信

    UNIX网络编程:socket fork()多进程 实现clients/server通信 随笔介绍了通过fork()多进程实现了服务器与多客户端通信。但除了多进程能实现之外,多线程也是一种实现方式。 重要的是,多进程和多线程是涉及操作系统层次。随笔不仅要利用pthread_create()实现多线程编程,也要理解线

    2024年02月06日
    浏览(55)
  • C++11线程池和Linux C++线程对比使用

    2024年02月09日
    浏览(38)
  • dockerfile传参问题和权限问题

    在工作中,使用dockerfile构建镜像的时候,随着业务越来越多,流水线也越来越多,这时候就需要构建一些通用流水线,而通用流水线最大的问题,就是需要传递参数,通过传递不同的参数去构建不同的服务 1、–build-arg 这个参数可以让你在docker build的时候,指定需要传递的参

    2023年04月26日
    浏览(35)
  • 【Java|多线程与高并发】线程安全问题以及synchronized使用实例

    Java多线程环境下,多个线程同时访问共享资源时可能出现的数据竞争和不一致的情况。 线程安全一直都是一个令人头疼的问题.为了解决这个问题,Java为我们提供了很多方式. synchronized、ReentrantLock类等。 使用线程安全的数据结构,例如ConcurrentHashMap、ConcurrentLinkedQueue等

    2024年02月09日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包