posix对线程的调整

fork

  当多线程进程调用fork创建子进程时,从fork返回时,只有调用fork的线程在进程内存在(其他线程在子进程中不存在,好比调用pthread_exit退出,不再拥有私有数据destructors或清除处理函数),其他线程状态仍保留为与调用fork时相同的状态。在子进程中,线程在与父进程中有相同的状态(互斥量(在父进程中被锁住,子进程也被锁住),数据键值),调用fork时同步在对象上等待的任何线程不再等待。

  子进程将自动继承父进程中互斥锁(条件变量与之类似)的状态。也就是说,父进程中已经被加锁的互斥锁在子进程中也是被锁住的。这就引起了-一个问题:子进程可能不清楚从父进程继承而来的互斥锁的具体状态(是加锁状态还是解锁状态)。这个互斥锁可能被加锁了,但并不是由调用fork函数的那个线程锁住的,而是由其他线程锁住的。如果是这种情况,则子进程若再次对该互斥锁执行加锁操作就会导致死锁

  此函数允许你的代码越过fork调用保护数据和不变量

int pthread_atfork(void (*prepare)(void),void (*parent)(void), void(*child)(void) );
  1. prepare:在父进程调用fork之前调用,这里可以获取父进程定义的所有锁.处理器以正确的顺序锁住相关的代码,使用的互斥量以阻止死锁的发生。调用fork的线程将在prepare fork中阻塞直到他锁住了所有的互斥量,这保证其他线程不能锁住某个互斥量或修改进程所需的数据
  2. parent fork:在创建出子进程后fork返回前在父进程的进程环境中调用的,prepare获得的锁进行解锁。开锁所有的互斥量允许父进程和所有线程继续工作
  3. child fork:fork执行后子进程内调用,与parent fork一样,有时需重置程序或库的状态(重置计数器,释放堆存储器)

  如果使用一个不提供fork的处理器(来为fork调用准备合适的互斥量),当调用printf时子进程可能会挂起,因为子线程调用fork时,父进程中的其他线程会锁住互斥量

  当父进程有多线程时,子进程继承父进程所有的互斥量、读写锁和条件变量的状态,子进程只存在一个线程,它是由父进程中调用fork的线程的副本构成的。如果父进程中的线程占有锁(任一线程),子进程同样占有这些锁。如果父进程马上调用exec,老的地址空间被丢弃,所以锁的状态无关紧要。否则,则要清除锁的状态。
  (1)如果子进程并不访问从父进程继承来的互斥量,不使用pthread_atfork是否会影响父进程,子进程的运行?
1)执行顺序(“|”代表线程的生命周期):
  父进程主线程:|                                                          ,lock, unlock           |
  父进程子线程:      |    lock, fork,           ,unlock                                |
  子进程主线程:                                |                                                   |2)结果:
   父进程的new thread, 能够正常unlock, 主线程能够正常lock, unlock
  不使用pthread_atfork并不会影响父进程,子进程的运行。
 (3)代码:
    为了简化实验代码,使用了sleep来控制线程之间的同步。
(2)如果子进程也需要访问从父进程继承来的locked互斥量,不使用pthread_atfork是否会影响父进程,子进程的运行?
1)执行顺序(“|”代表线程的生命周期):
  父进程主线程:|                                                                                  |
  父进程子线程:    |    lock, fork,           unlock,lock, unlock   |
  子进程主线程:                              | run,     lock, unlock        |2)结果:
  父进程中的子线程,能够正常unlock, 但是子进程一直没能够getlock
  不使用pthread_atfork并不会影响父进程,但是会影响子进程的运行。
(3)代码:
  为了简化实验代码,使用了sleep来控制线程之间的同步。

void * thr_main(void * arg)
{
    printf("parent process: new thread start \n");
    pthread_mutex_lock(&lock);
    printf("parent process: new thread get lock \n");
 
    pid_t pid;
    if((pid = fork()) < 0)
    {
        perror("fork");
        pthread_exit(0);
    }
    else if(pid == 0) // child process
    {
        printf("child process: start\n");
        printf("child process: unlock and get lock \n");
        pthread_mutex_unlock(&lock); 
        pthread_mutex_lock(&lock);               
        printf("child process: exit\n");
    }
    else // parent process
    {
        sleep(10);
        pthread_mutex_unlock(&lock);
        printf("parent process: new thread unlock \n");
        printf("parent process: new thread exit \n");
 
    }
    return ((void*)0);
}
int main(void)
{
    printf("parent process: main thread start \n");
    int err = 0;
    pthread_t tid;
    
    err = pthread_create(&tid, NULL, thr_main, NULL);
    if(err != 0)
    {
        perror("pthread_create");
        exit(1);
        }
    sleep(50);
    printf("parent process: main thread exit \n");
    return 0;
}
(3)如果子进程主动释放从父进程继承来的互斥量,不使用pthread_atfork是否会影响父进程,子进程的运行?
1)执行顺序(“|”代表线程的生命周期):
  父进程主线程:|                                                                                    |
  父进程子线程:    |    lock, fork,                                    , unlock   |
  子进程主线程:                              | run,  unlock, lock   |2)结果:
  父子进程都能够正常运行。
(3)代码:
  为了简化实验代码,使用了sleep来控制线程之间的同步。

 exec

  终止进程内除调用exec的线程外的所有线程,他们不执行清除处理器或线程私有数据destructor——线程只是简单地消失。

  除shared互斥量,条件变量(使用PTHREAD_PROCESS_SHARED属性创建的互斥量和条件变量),因为共享内存要被一些进程映射,应该解锁当前进程可能锁住的任何shared互斥量

pthread_exit

  在主函数中调用该函数会使整个进程终止,与进程所有相关的内存和线程消失,线程不会执行清楚处理器或线程私有数据destructor函数,

 stdio

  stdio是线程安全的,包含输出缓冲区和文件状态指定静态存储区。实现用互斥量或信号灯同步机制

  要实现stdio操作不被中断的顺序执行

void flockfile(FILE *filehandle);
int ftrylockfile(FILE *filehandle);
void funlockfile(FILE *filehandle);

  必须要锁住两个文件流时,要在输出流之前锁住输入流。

int getc_unlocked(FILE *stream); 
int putc_unlocked(int c,FILE *stream);
int getchar_unlocked(void);
int putchar_unlocked(int c);

  这些函数不执行任何锁操作,因此必须在这些操作附近使用flockfile和funlockfile,如果要想读写单个字符,通常应该使用加锁的变量而非锁住流文件,调用新的get,put函数然后解锁流文件。

  putchar内部被同步以确保stdio缓冲区不被破坏,但是单个字符可以以任意顺序出现。

 线程安全函数

/* 用户和终端id
    这些函数将返回到调用者指定的缓冲区
    成功返回0失败返回一个错误数字,getlogin_r和ttyname_r还可能返回ERANGE以表明名字缓冲区大小
*/
int getlogin_r(char *name,size_t namesize);//namesize至少是LOGIN_NAME_MAX
char *ctermid(char *s);
int ttyname_r(int fildes,char *name,size_t namesize);//namesize至少是TTY_NAME_SIZE

/*目录搜索
    与readdir差别:不返回指向那个入口的函数指针,而是将入口拷贝到entry指定的缓冲区
    成功返回0,并设置result指向的指针,
    搜索到目录流的结尾,返回0并置空result
    失败返回一个如EBADF之类的错误数字
*/
int readdir_r(DIR *dirp,struct dirent *entry,struct dirent **result);

/*字符串token
    返回字符串s的下一个token,没有更多的token时,返回null。当第一次调用时,参数s保存指向字符串的指针,在后续的调用中,
    为返回该字符串的后序token,s必需被置空,。
    lasts值保存函数在字符串内的位置,并在随后的每个调用中你必须返回同样的lasts值,
*/
char *strtok_r(char *s,const char *sep,char **lasts);

/*
    输出缓冲区(buf,result)由调用者提供,而不是返回一个指向函数内部的静态存储指针,buf至少为指向26个字节的字符串
*/
char *asctime_r(const struct tm *tm,char *buf);
char *ctime_r(const time_t *clock,char *buf);
struct tm *gmtime_r(const time_t *clock,struct tm* result);
struct tm *localtime_r(const time_t *clock,struct tm *result);

/*
    种子被维持在调用者提供的存储而非使用函数静态内部的静态存储,该接口的主要问题是:在一个程序内让所有的应用和代码库共享
    单个种子通常是不实际的。结果是应用程序和各个库通常将产生分离的随机数流,这样即使没有创建线程(创建线程可能将rand调用的)
    内部顺序改变,因此不管怎样会改变结果,用rand代替rand_r会产生不同的结果
*/
int readr(unsigned int *seed);

/*组和用户数据库
    针对特定的组和用户,将组和用户记录一个拷贝(分别是grp和pwd),保存到由buffer和bufsize指定的一个缓冲区中
    返回0成功,一个错误是数字失败(如:缓冲区大小ERANE),如果记录不在组或passwd数据库中,则函数可能返回成功但将result置空
    如果记录被发现且缓冲区够大,则result指针指向buffer内的struct group或struct passwd结构记录
    缓冲区大小可以通过sysconf来设置,其中参数_SC_GETGR_R_SIZEMAX对于组数据,_SC_GETPW_R_SIZE_MAX对于用户数据
*/
//组数据库
int getgrgid_r(gid_t gid,struct group *grp,char *buffer,size_t bufszie,struct group **result);
int getgrnam_r(const char *name,struct group *grp,struct group **result);
//用户数据库
int getpwuid_r(uid_t uid,struct passwd *pwd,char *buffer,size_t bufsize,struct passwd **result);
int getpwnam_r(char *name,struct passwd *pwd,char *buffer,size_t bufsize,struct passwd **result);

 

转载于:https://www.cnblogs.com/tianzeng/p/9309978.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值