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指针。在这种情况下,应该检查返回值,并采取适当的错误处理措施。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

丁金金

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值