[原]UNP多线程编程技巧简单总结

刘生玺 18/09/27 23:41:07

1. 为新线程传递参数

错误代码示例:

    for (i = 0; i < N; i++)
    {
        pthread_create(&tid, NULL, &handle, &i);
    }

当当当,要提问啦!!! 以上这段代码会发生什么奇怪的事情吗?当线程去进行处理i的时候,如果cpu调度到主线程运行,就会改变i的值。因为传入的是地址,所以线程中使用的i就会被改变。这就会出现问题。那么我们如何给线程传递参数呐?一般有以下两种方法:

1.  传送值而不传送地址

2.  通过`new,malloc`传递
1.  传送值而不传送地址

for (i = 0; i < N; i++)
{
    pthread_create(&tid, NULL, &handle, (void *)i);
}


void *handle(void *arg)
{
    int i = (int)arg;
    。。。
    return 0;
}
 
2.  通过`new,malloc`传递

int *iptr = NULL;
for (i = 0; i < MAX; i++)
{
    iptr = (int *)malloc(sizeof(int));
    *iptr = i;
    pthread_create(&tid[i], NULL, handle, iptr);
}
void *handle(void *arg)
{
    int i = *((int *)arg);
    free(arg);
    。。。
    return 0;
}

2. 线程特定数据

   在这里我们分析并解决一个常见多线程编程错误:在函数中使用到静态变量 。为什么会错?原因就是这些函数中使用到的静态变量无法为不同的线程保存各自的值。如果一个线程要操作该值,只会操作的是最终的数值。解决方法有三种:

  1. 使用线程特定数据。使得这些变量在每个线程中都独自存在一份。线程私有的全局变量,仅在某个线程中有效,但却可以跨多个函数访问。
  2. 改变调用顺序。略
  3. 改变接口结构。避免使用静态变量,如果是改写我们前面的Recvline函数的话,就会降低效率!!!

比较常用的就是第一种方法。

相关函数如下:


int pthread_key_create(pthread_key_t *key, void (*destr_function) (void *));  
int pthread_key_delete(pthread_key_t key);  
  
int pthread_setspecific(pthread_key_t key, const void *pointer);  
void * pthread_getspecific(pthread_key_t key);  
  
pthread_once_t once_control = PTHREAD_ONCE_INIT;  
int pthread_once(pthread_once_t *once_control, void (*init_routine) (void));  

众所周知,进程中有多个线程,线程中又有线程特定的数据元素(POSIX限定元素数量 >= 128 个),系统为每一个进程维护一个称之为Key结构的数组。如下所示:

   在这里插入图片描述

标志:指示该数组元素是否在使用。初始化自然是`不在使用中`
析构函数指针:一个线程终止时释放线程特定数据的手段

另外,系统还会在进程内维护关于每个线程的一些信息。这些信息都在结构Pthread结构中保存。pkeys是128个指针数组,它与进程内的128个key息息相关。(其实我觉得可以认为是一样子的),结构如下:

在这里插入图片描述

重点理解:

   当调用pthread_key_create 后会产生一个所有线程都可见的线程特定数据的键值(第一个在key数组中不在使用的元素的索引,也会对应到pthread结构中。比如:发现key[1]空闲,就会返回1,其中pkey[1]也会被用的到。), **但是这个pkey[1]所指向的真实数据却是不同的!**虽然都是pkey[1], 但是他们并不是指向同一块内存,而是指向了只属于自己的实际数据, 因此, 如果线程0更改了pkey[1]所指向的数据, 而并不能够影响到线程 n ;

在这里插入图片描述

下面我来通过一个实例说明:

#include "../myhead.h"

static pthread_key_t global_key;
static pthread_once_t control_once = PTHREAD_ONCE_INIT; // 0
// 用来销毁每个线程所指向的实际数据
static void pth_destructor(void *ptr)
{
    printf("销毁线程。。。。。\n");
    free(ptr);
}
static void pro_once(void)
{
    printf("\t获取进程键\n");
    pthread_key_create(&global_key, pth_destructor); //第二个参数是线程释放函数
}
typedef struct
{
    int id;
    char *str;
} TT;
void *handle(void *arg)
{
    TT *tsd;
    char *tmp = ((TT *)arg)->str;
    int i = ((TT *)arg)->id;

    free(arg);

    pthread_once(&control_once, pro_once); //在进程范围内只被调用一次,其实就是取得 key[n] 和 pkey[n]的索引

    if ((tsd = pthread_getspecific(global_key)) == NULL) //测试在当前这个线程中有没有分配线程的实际数据
    {
        tsd = (TT *)malloc(sizeof(TT));
        tsd->id = i;
        tsd->str = tmp;
        pthread_setspecific(global_key, tsd); //存放线程特定数据
        printf("%d pthread setspecific, address: %p\n", i, tsd);
    }
    /*获取*/
    TT *temp = pthread_getspecific(global_key);
    printf("temp->id ==  %d ,temp->str == %s \n", temp->id, temp->str);
    sleep(5);

    /*再次获取*/
    temp = pthread_getspecific(global_key);
    printf("temp->id ==  %d ,temp->str == %s \n", temp->id, temp->str);
    
    pthread_exit(NULL);
}
int main()
{
    pthread_t tid1, tid2;
    int i;
    TT *iptr;
    for (i = 1; i < 3; i++)
    {
        iptr = (TT *)malloc(sizeof(TT));
        (*iptr).id = i;
        i == 1 ? ((*iptr).str = "11111111111") : ((*iptr).str = "222222222222222222222");
        pthread_create(&tid1, NULL, handle, iptr);
    }

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    pthread_key_delete(global_key);

    return 0;
}

运行结果:
在这里插入图片描述

可以看出,我们的缺可以通过同一个key找到各个线程中的独有的实际数据。更加具体的信息就不说了。

3. 实现主线程等待任一线程结束

都知道,pthread_join()函数只能等待某一个线程结束,而不能等待任一线程结束。那么当我们真的想要用到这一点的实现的时候有应该要用什么来实现呐?答案就是条件变量。远离就是:某个线程退出时,通过条件变量通知主线程。实现如下:


#include "../myhead.h"
#define MAX 10
/*利用条件变量实现 thr_join 等待任一线程结束 */
int ndone = 0;
pthread_mutex_t ndone_mutex;
pthread_cond_t ndone_cond;
int data[MAX];
int Decide = MAX;

void *handle(void *arg)
{
    sleep(5);

    pthread_mutex_lock(&ndone_mutex);
    printf("我要推出了哦!!我是 %d 号线程哦 !!!\n", pthread_self());
    ndone++;
    *((int *)arg) = 1;
    Decide--;
    pthread_cond_signal(&ndone_cond);
    pthread_mutex_unlock(&ndone_mutex);
    return 0;
}
int main(void)
{
    pthread_t tid[MAX];
    int i;
    Decide = MAX;
    for (i = 0; i < MAX; i++)
    {
        data[i] = 0;
        pthread_create(&tid[i], NULL, handle, &data[i]);
    }
    while (Decide > 0)
    {
        pthread_mutex_lock(&ndone_mutex);
        while (ndone == 0) //说明没有任何线程退出
        {
            pthread_cond_wait(&ndone_cond, &ndone_mutex);
        }
        //一定有某个线程退出 
        for (i = 0; i < MAX; i++)
        {
            if (data[i] == 1){
                printf("pthread_join 等待 %d 线程退出 \n",tid[i]);
                pthread_join(tid[i], NULL);
                data[i] = 0 ;
            }
        }
        pthread_mutex_unlock(&ndone_mutex);
    }
    return 0;
}

运行结果:
在这里插入图片描述

至于为什么每个条件变量都要关联一个互斥锁?是因为条件通常是线程之间共享的某个变量的值,允许不同线程设置和测试该变量就要求有一个与该变量关联的互斥锁。

作者:liushengxi_root 发表于 2018/09/27 23:41:07 原文链接 https://blog.csdn.net/liushengxi_root/article/details/82844298
阅读:41