linux线程学习(6)

1. 线程私有数据

  • 应用程序设计中有必要提供一种变量,使得多个函数多个线程都可以访问这个变量(看起来是个全局变量),但是线程对这个变量的访问都不会彼此产生影响(貌似不是全局变量哦),但是你需要这样的数据,比如errno。那么这种数据就是线程的私有数据,尽管名字相同,但是每个线程访问的都是数据的副本。

  • 在使用私有数据之前,你首先要创建一个与私有数据相关的键,要来获取对私有数据的访问权限 。这个键的类型是pthread_key_t
    int pthread_key_create(pthread_key_t key, void (*destructor)(voi8d));

  • 创建的键放在key指向的内存单元,destructor是与键相关的析构函数。当线程调用pthread_exit或者使用return返回,析构函数就会被调用。当析构函数调用的时候,它只有一个参数,这个参数是与key关联的那个数据的地址(也就是你的私有数据啦),因此你可以在析构函数中将这个数据销毁。键使用完之后也可以销毁,当键销毁之后,与它关联的数据并没有销毁哦
    int pthread_key_delete(pthread_key_t key);

  • 有了键之后,你就可以将私有数据和键关联起来,这样就就可以通过键来找到数据。所有的线程都可以访问这个键,但他们可以为键关联不同的数据。(这岂不是一个名字一样,而值却不同的全局变量么)
    int pthread_setspecific(pthread_key_t key, const void *value);
    将私有数据与key关联
    void *pthread_getspecific(pthread_key_t key);
    获取私有数据的地址,如果没有数据与key关联,那么返回空

实例练习:首先要创建一个与私有数据相关的键,要来获取对私有数据的访问权限 ,然后线程进行数据的关联,查看结果

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

pthread_key_t key;//定义一个键

void *thread_fun1(void *arg)
{
    printf("thread 1 start!\n");
    int a = 1;
    //将a和key关联
    pthread_setspecific(key, (void *)a);
    sleep(2);
    printf("thread 1 key->data is %d\n", pthread_getspecific(key));//得到键相关联的线程的私有数据
}
void *thread_fun2(void *arg)
{
    sleep(1);
    printf("thread 2 start!\n");
    int a = 2;
    //将a和key关联
    pthread_setspecific(key, (void *)a);
    printf("thread 2 key->data is %d\n", pthread_getspecific(key));//得到键相关联的线程的私有数据
}

int main()
{
    pthread_t tid1, tid2;

    //创造一个key
    pthread_key_create(&key, NULL);

    //创造新线程
    if(pthread_create(&tid1, NULL, thread_fun1, NULL))
    {
        printf("create new thread 1 failed\n");
        return;
    }
    if(pthread_create(&tid2, NULL, thread_fun2, NULL))
    {
        printf("create new thread 2 failed\n");
        return;
    }

    //等待新线程结束
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    pthread_key_delete(key);

    return;
}

运行结果:线程一和线程二所打印的同一个key关联的data值不一样。
这里写图片描述
2. 线程与fork

  • 当线程调用fork函数时,就为子进程创建了整个进程地址空间的副本,子进程通过继承整个地址空间的副本,也会将父进程的互斥量、读写锁、条件变量的状态继承过来。也就是说,如果父进程中互斥量是锁着的,那么在子进程中互斥量也是锁着的(尽管子进程自己还没有来得及lock),这是非常不安全的,因为不是子进程自己锁住的,它无法解锁。

  • 子进程内部只有一个线程,由父进程中调用fork函数的线程副本构成。如果调用fork的线程将互斥量锁住,那么子进程会拷贝一个pthread_mutex_lock副本,这样子进程就有机会去解锁了。或者互斥量根本就没被加锁,这样也是可以的,但是你不能确保永远是这样的情况。

  • pthread_atfork函数给你创造了这样的条件,它会注册三个函数
    int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));
    prepare是在fork调用之前会被调用的,parent在fork返回父进程之前调用,child在fork返回子进程之前调用。如果在prepare中加锁所有的互斥量,在parent和child中解锁所有的互斥量,那么在fork返回之后,互斥量的状态就是未加锁。

  • 可以有多个 pthread_atfork函数,这是也就会有多个处理程序(prepare,parent,child)。多个prepare的执行顺序与注册顺序相反,而parent和child的执行顺序与注册顺序相同

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
//定义互斥量并初始化为PTHREAD_MUTEX_INITIALIZER
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void *thread_fun(void *arg)
{
    sleep(1);//主线程先运行
    pid_t pid;
    pid = fork();//创建子进程
    if(pid==0)
    {
        pthread_mutex_lock(&mutex);//子进程加锁,导致锁死
        printf("child\n");
        pthread_mutex_unlock(&mutex);
    }
    if(pid>0)
    {
        pthread_mutex_lock(&mutex);//加锁阻塞
        printf("parent\n");
        pthread_mutex_unlock(&mutex);
    }
}

int main()
{
    pthread_t tid;
    //创建新线程
    if(pthread_create(&tid, NULL, thread_fun, NULL))
    {
        printf("create new thread failed\n");
        return;
    }

    pthread_mutex_lock(&mutex);//主线程进程对互斥量加锁
    sleep(2);
    pthread_mutex_unlock(&mutex);
    printf("main\n");
    pthread_join(tid, NULL);

    return;
}

在线程中创建子进程,子进程进行对互斥量加锁,此时互斥量是已经锁住的,并且不是自己加的锁,导致锁死,所以child没有打印。线程中的父进程是与主线程是一样的,此时互斥量还是加锁的,阻塞,2s后主线程进行对互斥量解锁,父进程就能加锁了,并打印parent。
这里写图片描述

ps进行查看
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值