UNIX环境高级编程-线程控制

目录

相关函数列表

线程属性的系统限制

线程属性的例子

线程互斥属性的例子

flock的例子

线程特定数据

线程和fork

线程和信号

pthread_kill

参考


 

 

 

相关函数列表

//线程属性,pthread_attr_t结构体保护的就是操作系统实现支持的所有线程属性  
//下面所有函数都是是成功返回0,否则返回错误编号  
#include   
int pthread_attr_init(pthread_attr_t *attr);  
int pthread_attr_destroy(pthread_attr_t *attr);  
  
//如果对某个线程终止状态不感兴趣,可以使用pthread_detach函数让系统在  
//线程退出时收回所占用的资源  
//可以detachstate为以下两个值  
//1.PTHREAD_CREATE_DETACHED  分离状态启动线程  
//2.PTHREAD_CREATE_JOINABLE    正常启动线程  
#include   
int pthread_attr_getdetachstate(const pthread_attr_t *attr,  
                                                     int *detachstate);  
int pthread_attr_setdetachstate(pthread_attr_t *attr, in *detachstate);  
  
  
//对线程栈属性进行管理  
#include   
int pthread_attr_getstack(const pthread_attr_t *restrict attr,  
                          void **restrict stackaddr, size_t *restrict stacksize);  
int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr,  
                                         size_t stacksize);  
  
//通过下面函数设置stacksize  
#include   
int pthread_attr_getstacksize(const pthread_attr_t *restrict attr,  
                                                size_t *restrict stacksize);  
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);  
  
  
//线程属性guardsize控制线程栈末尾以后用以避免溢出的口占内存的大小,这个  
//属性默认值是由具体实现来定义,但常用值是系统页大小,可以把其设置为0,  
//不允许属性的这种特征行为发生,在这种情况下,不会提供警戒缓冲区。同样,  
//如果修改了线程属性stackaddr,系统就认为我们将自己管理栈,警戒缓冲区  
//机制无效  
#include   
int pthread_attr_getguardsize(const pthread_attr_t *restrict attr,  
                                                 size_t *restrict guardsize);  
int pthread_attr_setguardsize(pthread_attr_t *attr,   
                                                 size_t guardsize);  
  
  
//互斥量属性,互斥量的返回值都是成功返回0,否则返回错误编号  
#include   
int pthread_mutexattr_init(pthread_mutexattr_t *attr);  
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);  
  
//修改进程共享属性  
#include   
int pthread_mutexattr_getpshared(const pthread_mutexattr_t   
                             *restrict attr, int *restrict pshared);  
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr,  
                                                        int pshared);  
  
//修改和获取健壮的互斥量属性  
#include   
int pthread_mutexattr_getrobust(const pthread_mutexattr_t   
                                *restrict attr, int *restrict robust);  
int pthread_mutexattr_setrobust(pthread_mutexattr_t *attr,   
                                                  int robust);  
  
//线程可以调用下面函数,指明与该互斥量相关的状态在互斥量解锁之前是一致的  
#include   
int pthread_mutex_consistent(pthread_mutex_t *mutex);  
  
//修改和获取互斥量属性  
#include   
int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict  
                                             attr, int *restirct type);  
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);  
  
  
//读写锁属性,读写锁的返回值都是成功返回0,否则返回错误编号  
#include   
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);  
int phtread_rwlockattr_destroy(pthread_rwlockattr_t *attr);  
  
//和互斥量一样,读写锁也有一对函数用于读取和设置进程共享属性  
#include   
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *restrict attr,  
                                                     int *restrict pshared);  
int pthraed_rwlockattr_setpshared(pthread_rwlockattr_t *attr,int pshared);  
  
  
  
//条件变量属性  
#include   
int pthread_condattr_init(pthread_condattr_t *attr);  
int pthread_condattr_destroy(pthread_condattr_t *attr);  
  
//条件变量也支持进程共享属性,可以被单个进程的多个线程使用  
//条件变量的返回值都是成功返回0,否则返回错误编号  
#include   
int pthread_condattr_getpshared(const pthread_condattr_t *restrict attr,  
                                   int *restrict pshared);  
int pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared);  
  
//条件变量可以设置时钟属性  
#include   
int pthread_condattr_getclock(const pthread_condattr_t *restrict attr,  
                              clockid_t *restrict clock_id);  
int pthread_condattr_setclock(pthread_condattr_t *atr, clockid_t clock_id);  
  
  
  
//屏障属性(屏障的返回值都是成功返回0,否则返回错误编号)  
#include   
int pthread_barrierattr_init(pthread_barrierattr_t *attr);  
int pthread_barrierattr_destroy(pthread_barrierattr_t *attr);  
  
//屏障的进程共享属性  
int pthraed_barrierattr_getpshared(const pthread_barrierattr_t *restrict attr,  
                                   int *restrict pshared);  
int pthread_barrierattr_setpshared(pthread_barrierattr_t *attr, int pshared);  
  
  
  
//POSIX.1提供了安全方式管理FILE对象的方法,可以使用下面方式获取给定FILE对象关联的锁,这个  
//锁是递归的,当占有这把锁后可以再次获取该锁,不会导致死锁。虽然这种锁的具体实现并无规定,  
//但要求所有操作FILE对象的标准I/O例程的动作行为必须看起来就像他们内部调用了flockfile和  
//funlockfile,以下函数都是成功返回0,否则返回错误编号  
#include   
int ftrylockfile(FILE *fp);  
void flockfile(FILE *fp);  
void funlockfile(FILE *fp);  
  
//如果标准I/O例程都获取他们各自的锁,那么在做一次一个字符的I/O时就会出现严重的性能下降,在  
//这种情况下,需要对每一个字符的读写操作都进行获取锁和释放锁的动作。为了避免这种开销,出现  
//了不加锁版本的基于字符的标准I/O例程  
#include   
int getchar_unlocked(void);  
int getc_unlocked(FILE *fp);   //这两个函数若成功返回下一个字符,若遇到文件尾或出错返回EOF  
int putchar_unlocked(int c);  
int putc_unlocked(int c, FILE *fp);  
  
  
//线程特定数据(thread0specific data),也成为线程私有数据(thread-private data),是存储和存储  
//某个特定线程相关数据的一种机制,每个线程都有自己单独的数据副本,而不需要担心与其他线程的  
//同步访问问题  
#include   
int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void*));  
  
//取消与现场特定数据值之间关联关系  
#include   
int pthread_key_delete(pthread_key_t key);  
  
//解决竞争条件  
#inlude   
pthread_once_t initflag = PHTREAD_ONCE_INIT;  
int pthread_once(pthread_once_t *initflag, void (*initfn)(void));  
  
//通过下面函数设置和获取线程特定数据的地址  
#include   
void *pthread_getspecific(pthread_key_t key);  
int pthread_setspecific(pthread_key_t key, const void *value);  
  
  
  
//线程取消,取消状态可以是PTHREAD_CANCEL_ENABLE或者PTHREAD_CANCEL_DISABLE  
#include   
int pthread_setcancelstate(int state, int *oldstate);  
  
//添加自己的取消点  
#include   
void pthread_testcancel(void);  
  
//修改取消类型,参数type可以使PTHREAD_CANCEL_DEFERRED,或者PTHREAD_CANCEL_ASYNCHRONOUS  
#include   
int pthread_setcanceltype(int type,int *oldtype);  
  
  
//线程和信号  
#include   
int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);  
  
//等待一个或多个信号出现  
#include   
int sigwait(const sigset_t *restirct set, int *restrict signop);  
  
//要把信号发给进程可以调用kill,要把信号发给线程可以调用pthread_kill  
#include   
int pthread_kill(pthread_t thread, int signo);  
  
//线程和fork,最多可以安装3个帮助清理锁的函数  
//prepare fork处理程序由父进程在fork创建子进程前调用,这个fork处理程序的任务是获取父进程  
//   定义的所有锁  
//parent fork处理程序是在fork创建子进程以后,返回之前在父进程上下文中调用的。这个fork处理  
//   程序的任务是对prepare fork处理程序获取的所有锁进行解锁  
//child fork处理程序在fork返回之前在子进程上下文中调用。与parent fork处理程序一样,child  
//   fork处理程序也必须释放prepare fork处理程序获取的所有锁  
#include   
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*chiild)(void));   

 

 

线程属性的系统限制

可以通过sysconf函数查询

限制名称 描述 name参数

PTHREAD_DESTRUCTOR

_ITERATIONS

 线程退出时操作系统实现试图销毁

特定数据段的最大次数

_SC_THREAD_DESTRUCTOR_

ITERATIONS 

PTHREAD_KEYS_MAX 进程可以创建的键的最大E数目  _SC_THREAD_KEYS_MAX 
PTHRAD_STACK_MIN 一个线程的栈可用的最小字节数  _SC_THREAD_STACK_MIN 
PTHREAD_THREADS_MAX 进程可以创建的最大线程数 _SC_THREAD_THREADS_MAX

 

pthread接口允许我们通过设置每个对象关联的不同属性来细调线程和同步对象的行为。通常,管理这些

属性的函数都遵循相同的模式

1)每个对象与它自己类型的属性对象进行关联(线程与线程属性关联,互斥量与互斥量属性关联等)。一个

   属性对象可以代表多个属性。属性对象对应用程序来说是不透明的。这意味着应用程序并不需要了解

   有关属性对象内部结构的详细细节,这样可以增强应用程序的可移植性。取而代之的是需要提供相应的

   函数来管理这些属性对象

2)有一个初始化函数,把属性设置为默认值

3)还有一个销毁属性对象的函数。如果初始化函数分配了与属性对象管理的资源,销毁函数负责释放这些

    资源

4)每个属性都有一个从属性对象中获取属性值的函数。由于函数成功时会返回0,失败时会返回错误编号,

   所以可以通过把属性值存储在函数的某一个参数执行的内存单元中,把属性值返回给调用者

5)每个属性都有一个设置属性值的函数。在这种情况下,属性值作为参数按值传递

 

POSIX.1定义的线程属性

名称 描述
detachstat 线程的分离状态属性
guardsize 线程栈末尾的警戒缓冲区大小(字节)
stackaddr 线程栈的最低地址
stacksize 线程栈的最小长度(字节)

线程属性的例子

#include 
#include 
#include 
#include 
#include 
#include 
#define false 0
#define true 1
#define bool int



int stack_size = 1024*1024*20;

void *fun() {
    printf("fun thread %d\n",pthread_self());
    pthread_exit(NULL);
}



int main(int argc, char *argv[]) {

    pthread_attr_t attr;
    pthread_t p1;
    void *stack = malloc(sizeof(stack_size));
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
    //pthread_attr_setstack(&attr,stack,stack_size);
    pthread_attr_setdetachstate(&attr,stack_size);
    pthread_create(&p1,&attr,fun,(void*)1);
    

    sleep(2);
    pthread_attr_destroy(&attr);
    free(stack);
    printf("end main...\n");
    return 0;
}

//执行结果
fun thread -913426688
end main...

 

 

互斥量类型行为

互斥量类型

没有解锁时

重新加载?

不占用

时解锁?

在已解锁

时解锁?

说明

PTHREAD_MUTEX

_NORMAL

死锁 未定义 未定义

一种标准互斥量类型,不做任何

特殊的错误检查或死锁检测

PTHREAD_MUTEX_

ERRORCHECK

返回错误 返回错误 返回错误

此互斥量类型提供错误检查

PTHREAD_MUTEX_

RECURSIVE

允许 返回错误 返回错误

此互斥量类型允许同一线程在互斥量解锁

之前对该互斥量进行多次加锁。递归互斥量维护

锁的计数,在解锁次数和加锁次数不相同的情况

下,不会释放锁。所以如果对一个递归互斥量

加锁两次,然后解锁一次,那么这个互斥量将

依然处于加锁状态,对它再次解锁以前不能释放

该锁。

PTHREAD_MUTEX_

DEFAULT

未定义 未定义 未定义

此互斥量类型可以提供默认特性和行为。操作

系统再实现它的时候可以把这种类型自由的映射

到掐互斥量类型中的一种。如Linux3.2.0把这种

类型映射为普通的互斥量类型而FreeBSD则把

它映射为错误检查互斥量类型

线程互斥属性的例子

#include 
#include 
#include 
#include 
#include 
#include 
#define false 0
#define true 1
#define bool int

pthread_mutex_t mutex;
int num = 100;

void lock() {
    pthread_mutex_lock(&mutex);
}

void unlock() {
    pthread_mutex_unlock(&mutex);
}

void *foo() {
    int i=0;
    for(;i<10;i++) {
        lock();
        num++;
    }
    sleep(1);
    i=0;
    for(;i<10;i++) {
        unlock();
    }
    printf("foo ok...\n");
    pthread_exit(NULL);
}


int main(int argc, char *argv[]) {

    pthread_mutexattr_t  attr;
    pthread_t p1;   
    void *ret1;

    pthread_mutexattr_init(&attr);
    //PTHREAD_PROCESS_SHARED设置为多进程共享模式
    pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
    //PTHREAD_MUTEX_ROBUST设置为健壮的互斥量,即使线程挂了也会返回
    pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST);
    //PTHREAD_MUTEX_RECURSIVE允许多次锁定一个对象
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);

    pthread_mutex_init(&mutex,&attr);
    pthread_mutex_consistent(&mutex);

    pthread_create(&p1,NULL,foo,(void*)1);
    pthread_join(p1,&ret1);

    pthread_mutex_destroy(&mutex);
    pthread_mutexattr_destroy(&attr);
    printf("num->%d\n",num);
    printf("main ok...\n");
    return 0;
}

//执行结果
foo ok...
num->110
main ok...

 

重入

如果一个函数在相同 时间点可以被多个线程安全的调用,就称该函数是线程安全的

 

flock的例子

#include 
#include 
#include 
#include 
#include 
#include 
#define false 0
#define true 1
#define bool int


char *file_name = "x.log";
void *foo(void *msg) {
    FILE *f = fopen(file_name,"r");
    flockfile(f);
    char buf[100];
    int read_count = fread(buf,sizeof(char),100,f);
    printf("foo.....\n");
    fwrite(buf,sizeof(char),read_count,stdout);
    printf("\nfoo... end\n");
    funlockfile(f);
    pthread_exit(NULL);
}


int main(int argc, char *argv[]) {

    pthread_t p1;
    void *ret1;
    char buf[100];
    pthread_create(&p1,NULL,foo,(void*)1);
    //sleep(1);
    FILE *f = fopen(file_name,"r");

    flockfile(f);
    int read_count = fread(buf,sizeof(char),100,f);   
    printf("main...\n");
    fwrite(buf,sizeof(char),read_count,stdout);
    printf("\nmain write ok\n");
    funlockfile(f);
    pthread_join(p1,&ret1);
    printf("###################   main end!\n");
    return 0;
}

//执行结果
main...
aaaaa
bbbbb
ccccc
ddddd

main write ok
foo.....
aaaaa
bbbbb
ccccc
ddddd

foo... end
###################   main end!

 

 

 

线程特定数据

线程特定数据(thread-specific data),也称为线程私有数据(thread-private data),是存储和查询某个特定

线程相关数据的一种机制。我们把这种数据称为线程特定数据或线程私有数据的原因是  希望每个线程可以

访问他们自己单独的数据副本,而不需要担心与其他线程的同步访问问题。

线程模型促进了线程中数据和属性的共享,为什么有人想在这种的模型中促进阻止共享的接口呢?

1)有时候需要维护每线程(per-thread)的数据,因为线程ID并不能保证是小而连续的整数,所以就不能简单

   的分配一个每线程数据数组,用线程ID作为数组的索引。即使线程ID确实是小而连续的整数,我们可能

   还希望有一些额外的保护,防止某个线程的数据与其他线程的数据相混淆

2)提供了让基于进程的接口适应多线程环境的机制,一个明显的实例就是errno,以前的接口都是进程

  上下文全局可访问的整数,系统调用和库例程调用或执行失败时设置的,把它作为操作失败时负数的结果。

  所以把errno定义为线程私有数据

 

线程特定数据的例子

#include 
#include 
#include 
#include 
#include 
#include 
#define false 0
#define true 1
#define bool int

pthread_key_t key;


void destroy(void *msg) {
    printf("end foo....\n");
}

void *foo(void *arg) {
    pthread_setspecific(key,arg);
    int *num = (int*)pthread_getspecific(key);
    *num = (*num) + 100;
    printf("modify num->%d, running in %s\n",*num, pthread_self());
    pthread_exit(NULL);

}

int main(int argc, char *argv[]) {
    pthread_t p1,p2;
    void *ret1, *ret2;
    int a = 1;
    int b = 2;
    pthread_key_create(&key,destroy);
    pthread_create(&p1,NULL,foo,&a);
    pthread_create(&p2,NULL,foo,&b);


    pthread_join(p1,&ret1);
    pthread_join(p2,&ret2);
    pthread_key_delete(key);
    printf("end main...\n");
    return 0;
}

//执行结果
modify num->101, running in 
end foo....
modify num->102, running in 
end foo....
end main...

 

 

线程和信号

每个线程都有自己的信号屏蔽字,但是信号的处理是进程中所有线程共享的

这意味着单个线程可以阻止某些信号,但当某个线程修改了与某个给定信号相关的处理行为以后,所有的

线程都必须共享这个处理行为的改变

 

线程和fork

子进程从父进程继承了每个互斥量,读写锁和条件变量,如果父进程包含一个以上的线程,子进程在fork

返回以后,如果紧接着不是马上调用exec的话,就需要清理锁状态

pthread_atfork是POSIX标准,在编译时要加上-pthread
-lpthread是老版本的gcc编译器用的,在新版本中应该用-pthread取代-lpthread

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define false 0
#define true 1
#define bool int

pthread_mutex_t lock1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t lock2 = PTHREAD_MUTEX_INITIALIZER;

void prepare() {
    printf("prepare lock...\n");
    pthread_mutex_lock(&lock1);
    pthread_mutex_lock(&lock2);
}

void parent() {
    printf("parent unlock\n");
    pthread_mutex_unlock(&lock1);
    pthread_mutex_unlock(&lock2);
}

void child() {
    printf("child unlock\n");
    pthread_mutex_unlock(&lock1);
    pthread_mutex_unlock(&lock2);
}

void *foo(void *msg) {
    printf("child thread foo ...\n");
    pause();
    pthread_exit(NULL);
}


int main(int argc, char *argv[]) {
    pthread_t p1;
    pid_t pid;
    pthread_atfork(prepare,parent,child);
    pthread_create(&p1,NULL,foo,(void*)0);

    sleep(2);
    printf("main -> fork\n");   
    pid = fork();
    if(pid > 0) { //parent
        printf("......parent return fork\n");
    }
    else if(pid == 0) { //child 
        printf("....child return fork\n");
    }
    else {
        printf("fork error\n)");
        exit(-1);
    }

    printf("thr program execute over!\n");
    return 0;
}

//执行结果,注意子进程的输出和父进程输出是分开的
child thread foo ...
main -> fork
prepare lock...
parent unlock
......parent return fork
thr program execute over!
[root@.......]# child unlock
....child return fork
thr program execute over!

 

线程和信号

#include
#include
#include

static void sig_alrm(int signo);
static void sig_init(int signo);
int main() {
        sigset_t set;
        int sig;
        sigemptyset(&set);
        sigaddset(&set, SIGALRM);
        pthread_sigmask(SIG_SETMASK, &set, NULL);//阻塞SIGALRM信号

        signal(SIGALRM, sig_alrm);
        signal(SIGINT, sig_init);
        sigwait(&set, &sig);//sigwait只是从未决队列中删除该信号,并不改变信号掩码。也就是,当sigwait函数返回,它监听的信号依旧被阻塞。
        switch(sig) {
            case 14:
                printf("sigwait, receive signal SIGALRM\n");
                /*do the job when catch the sigwait*/
                break;
            default:
                break;
        }
        sigdelset(&set, SIGALRM);
        pthread_sigmask(SIG_SETMASK, &set, NULL);

        for(;;) {

        }

        return 0;
}

static void sig_alrm(int signo) {
        printf("after sigwait, catch SIGALRM\n");
        fflush(stdout);
        return ;
}

static void sig_init(int signo) {
        printf("catch SIGINT\n");
        return ;
}

//执行
kill -SIGINT 32128
kill -SIGINT 32128
kill -SIGINT 32128

//结果
catch SIGINT
catch SIGINT
catch SIGINT

//执行
kill -SIGALRM 32128
kill -SIGALRM 32128
kill -SIGALRM 32128

//结果
sigwait, receive signal SIGALRM
after sigwait, catch SIGALRM
after sigwait, catch SIGALRM

 

pthread_kill

#include
#include
#include

void handler(int sig_num, siginfo_t *info, void *arg) {
    printf("catch signum->%d, thread->%u\n",sig_num, pthread_self());
}

void *foo(void *msg) {
    struct sigaction act;
    act.sa_sigaction = handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_SIGINFO;
    sigaction(SIGINT, &act, NULL);
   
    while(1) {
        pause();
    }
}


int main(int argc, char *argv[]) {

    pthread_t  p1;
    void *ret1;
    
    pthread_create(&p1,NULL,foo,(void*)1);

    sleep(1);
    printf("send sig SIGINT to thread %u\n",p1);   
    pthread_kill(p1, SIGINT);

    sleep(1);
    printf("send sig SIGQUIT to thread %u\n",p1);
    pthread_kill(p1, SIGQUIT);

    return 0;
}


//执行结果
send sig SIGINT to thread 1079043840
catch signum->2, thread->1079043840
send sig SIGQUIT to thread 1079043840


//用strace分析程序,pthrea_kill最终是用 tgkill() 这个系统函数完成的
//如果线程对某个信号执行默认动作(退出程序),会导致整个程序的退出
。。。
[pid 32201] pause(
 
[pid 32200] <... nanosleep resumed> 0x7ffd3e215580) = 0
[pid 32200] fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0
[pid 32200] mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fc1da0fd000
[pid 32200] write(1, "send sig SIGINT to thread 365008"..., 37send sig SIGINT to thread 3650086656
) = 37
[pid 32200] tgkill(32200, 32201, SIGINT) = 0
[pid 32200] rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
[pid 32200] rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
[pid 32200] rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
[pid 32200] nanosleep({1, 0},  
[pid 32201] <... pause resumed> )       = ? ERESTARTNOHAND (To be restarted if no handler)
[pid 32201] --- SIGINT {si_signo=SIGINT, si_code=SI_TKILL, si_pid=32200, si_uid=0} ---
[pid 32201] write(1, "catch signum->2, thread->3650086"..., 36catch signum->2, thread->3650086656
) = 36
[pid 32201] rt_sigreturn({mask=[]})     = -1 EINTR (Interrupted system call)
[pid 32201] pause( 
[pid 32200] <... nanosleep resumed> 0x7ffd3e215580) = 0
[pid 32200] write(1, "send sig SIGQUIT to thread 36500"..., 38send sig SIGQUIT to thread 3650086656
) = 38
[pid 32200] tgkill(32200, 32201, SIGQUIT) = 0
[pid 32200] exit_group(0)               = ?
[pid 32201] +++ exited with 0 +++
+++ exited with 0 +++

 

 

参考

pthread_mutexattr_setpshared(3)

pthread_mutexattr_setrobust

Unix环境高级编程(十二)线程控制

linux中使用信号--sigwait()和pthread_sigmask()

Linux线程-pthread_kill

 

你可能感兴趣的:(Linux,c语言)