为什么对多线程编程这么怕?pthread,sem,mutex,process

Posted 积少成多

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么对多线程编程这么怕?pthread,sem,mutex,process相关的知识,希望对你有一定的参考价值。

转自http://blog.chinaunix.net/uid-20788636-id-1841334.html

1.线程创建和退出
创建线程实际上就是确定调用该线程函数的入口点,这里通常使用的函数是pthread_create。在线程创建以后,就开始运行相关的线程函数,在该函数运行完之后,该线程也就退出了,这也是线程退出一种方法。另一种退出线程的方法是使用函数pthread_exit,这是线程的主动行为。在线程中使用pthread_exit来代替进程中的exit。
由于一个进程中的多个线程是共享数据段的,因此通常在线程退出之后,退出线程所占用的资源并不会随着线程的终止而得到释放。正如进程之间可以用wait()系统调用来同步终止并释放资源一样,线程之间也有类似机制,那就是pthread_join()函数。pthread_join可以用于将当前线程挂起,等待线程的结束。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源就被收回。
    
函数格式
 pthread_create
 所需头文件 #include
    函数原型 int pthread_create ((pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg))
    函数传入值 thread:       线程标识符
               attr:         线程属性设置
               start_routine:线程函数的起始地址
               arg:          传递给start_routine 的参数
    函数返回值 成功:0
               出错:-1

    pthread_exit

    所需头文件 #include
    函数原型 void pthread_exit(void *retval)
    函数传入值 Retval :pthread_exit()调用者线程的返回值,可由其他函数如pthread_join来检索获取

    pthread_join

    所需头文件 #include
    函数原型 int pthread_join ((pthread_t th, void **thread_return))
    函数传入值 th:等待线程的标识符
               thread_return:用户定义的指针,用来存储被等待线程的返回值(不为NULL 时)
    函数返回值 成功:0
               出错:-1
2.修改线程属性

    pthread_create函数的第二个参数——线程的属性,将该值设为NULL,也就是采用默认属性,线程的多项属性都是可以更改的。这些属性主要包括绑定属性、分离属性、堆栈地址、堆栈大小、优先级。其中系统默认的属性为非绑定、非分离、缺省1M的堆栈、与父进程同样级别的优先级。
    a.绑定属性
    Linux中采用“一对一”的线程机制,也就是一个用户线程对应一个内核线程。绑定属性就是指一个用户线程固定地分配给一个内核线程,因为CPU 时间片的调度是面向内核线程(也就是轻量级进程)的,因此具有绑定属性的线程可以保证在需要的时候总有一个内核线程与之对应。而与之相对的非绑定属性就是指用户线程和内核线程的关系不是始终固定的,而是由系统来控制分配的。
    b.分离属性
    分离属性是用来决定一个线程以什么样的方式来终止自己。在非分离情况下,当一个线程结束时,它所占用的系统资源并没有被释放,也就是没有真正的终止。只有当pthread_join()函数返回时,创建的线程才能释放自己占用的系统资源。而在分离属性情况下,一个线程结束时立即释放它所占有的系统资源。这里要注意的一点是,如果设置一个线程的分离属性,而这个线程运行又非常快,那么它很可能在pthread_create 函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这时调用pthread_create的线程就得到了错误的线程号。
    这些属性的设置都是通过一定的函数来完成的,通常首先调用pthread_attr_init函数进行初始化,之后再调用相应的属性设置函数。设置绑定属性的函数为pthread_attr_setscope,设置线程分离属性的函数为pthread_attr_setdetachstate,设置线程优先级的相关函数为pthread_attr_getschedparam(获取线程优先级)和pthread_attr_setschedparam(设置线程优先级)。在设置完这些属性后,就可以调用pthread_create函数来创建线程了。

    pthread_attr_init
    函数原型 int pthread_attr_init(pthread_attr_t *attr)
    函数传入值 attr:线程属性
    函数返回值 成功:0
               出错:-1

    pthread_attr_setscope
    函数原型 int pthread_attr_setscope(pthread_attr_t *attr, int scope)
    函数传入值 attr:线程属性
               scope: PTHREAD_SCOPE_SYSTEM:绑定
                      PTHREAD_SCOPE_PROCESS:非绑定
    函数返回值 成功:0
               出错:-1

    pthread_attr_setdetachstate
    函数原型 int pthread_attr_setscope(pthread_attr_t *attr, int detachstate)
    函数传入值 attr:线程属性
               detachstate PTHREAD_CREATE_DETACHED:分离
               PTHREAD _CREATE_JOINABLE:非分离
    函数返回值 成功:0
               出错:-1

    pthread_attr_getschedparam
    函数原型 int pthread_attr_getschedparam (pthread_attr_t *attr, struct sched_param *param)
    函数传入值 attr:线程属性
               param:线程优先级
    函数返回值 成功:0
               出错:-1

    pthread_attr_setschedparam
    函数原型 int pthread_attr_setschedparam (pthread_attr_t *attr, struct sched_param *param)
    函数传入值 attr:线程属性
               param:线程优先级
    函数返回值 成功:0
               出错:-1
3.线程访问控制

    主要有互斥锁和信号量。

    a.mutex互斥锁线程控制

    mutex是一种简单的加锁的方法来控制对共享资源的存取。这个互斥锁只有两种状态,也就是上锁和解锁,可以把互斥锁看作某种意义上的全局变量。在同一时刻只能有一个线程掌握某个互斥上的锁,拥有上锁状态的线程能够对共享资源进行操作。若其他线程希望上锁一个已经上锁了的互斥锁,则该线程就会挂起,直到上锁的线程释放掉互斥锁为止。可以说,这把互斥锁使得共享资源按序在各个线程中操作。
    互斥锁的操作主要包括以下几个步骤。
        互斥锁初始化:pthread_mutex_init
        互斥锁上锁:pthread_mutex_lock
        互斥锁判断上锁:pthread_mutex_trylock
        互斥锁解锁:pthread_mutex_unlock
        消除互斥锁:pthread_mutex_destroy
    其中,互斥锁可以分为快速互斥锁、递归互斥锁和检错互斥锁。这三种锁的区别主要在于其他未占有互斥锁的线程在希望得到互斥锁时的是否需要阻塞等待。
    快速锁是指调用线程会阻塞直至拥有互斥锁的线程解锁为止。
    递归互斥锁能够成功地返回并且增加调用线程在互斥上加锁的次数。
    检错互斥锁则为快速互斥锁的非阻塞版本,它会立即返回并返回一个错误信息。

    pthread_mutex_init
    函数原型 int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
    函数传入值 Mutex:互斥锁
               PTHREAD_MUTEX_INITIALIZER:创建快速互斥锁
               Mutexattr PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP :创建递归互斥锁
               PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP :创建检错互斥锁
    函数返回值 成功:0
               出错:-1

    pthread_mutex_lock
    函数原型 int pthread_mutex_lock(pthread_mutex_t *mutex,)
             int pthread_mutex_trylock(pthread_mutex_t *mutex,)
             int pthread_mutex_unlock(pthread_mutex_t *mutex,)
             int pthread_mutex_destroy(pthread_mutex_t *mutex,)
    函数传入值 Mutex:互斥锁
    函数返回值 成功:0
               出错:-1
b.信号量线程控制

信号量也就是操作系统中所用到的PV原语,它广泛用于进程或线程间的同步与互斥。信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。
PV原语是对整数计数器信号量sem的操作。一次P操作使sem减一,而一次V操作使sem加一。进程(或线程)根据信号量的值来判断是否对公共资源具有访问权限。当信号量sem的值大于等于零时,该进程(或线程)具有公共资源的访问权限;相反,当信号量sem的值小于零时,该进程(或线程)就将阻塞直到信号量sem的值大于等于0为止。
    PV原语主要用于进程或线程间的同步和互斥这两种典型情况。若用于互斥,几个进程(或线程)往往只设置一个信号量sem,它们的操作流程如图9.2 所示。
    当信号量用于同步操作时,往往会设置多个信号量,并安排不同的初始值来实现它们之间的顺序执行.

sem_init用于创建一个信号量,并能初始化它的值。
sem_wait和sem_trywait相当于P操作,它们都能将信号量的值减一,两者的区别在于若信号量小于零时,sem_wait将会阻塞进程,而sem_trywait则会立即返回。
sem_post相当于V操作,它将信号量的值加一同时发出信号唤醒等待的进程。
sem_getvalue用于得到信号量的值。
sem_destroy用于删除信号量。

sem_init
所需头文件 #include
函数原型 int sem_init(sem_t *sem,int pshared,unsigned int value)
函数传入值 sem:信号量
     pshared:决定信号量能否在几个进程间共享。由于目前Linux 还没有实现进程间共享信号量,所以这个值只能够取0
                value :信号量初始化值
    函数返回值 成功:0
               出错:-1

    sem_wait
    函数原型   int sem_wait(sem_t *sem)
               int sem_trywait(sem_t *sem)
               int sem_post(sem_t *sem)
               int sem_getvalue(sem_t *sem)
               int sem_destroy(sem_t *sem)
    函数传入值 sem:信号量
函数返回值 成功:0
               出错:-1

================================

转自:http://blog.csdn.net/sharelearner/article/details/9750273

pthread_join是做什么的?

这个函数主要是为了解决资源回收的问题!

问题是什么呢?

在linux中,默认情况下,在一个线程被创建后,必须使用此函数对创建的线程进行资源回收,但也可以

设置Threads attributes(绑定属性、分离属性、堆栈地址、堆栈大小、优先级) 来设置当一个线程结束时,直接回收此线程所占用的资源.

其实在linux中,新建的线程并不是在原先的进程中,而是系统通过一个调用clone(),该系统copy了一个和原先进程完全一样的进程,并在这个进程中执行线程函数。

不过这个copy过程和fork不一样,copy出来的进程和原先的进程共享了所有的变量,运行环境。这样进程中的变量变动就在copy后的进程中体现出来了。

---!!!那么pthread_join函数是做啥用的呢?

代码中如果没有pthread_join,那么主线程很可结束从而使得整个进程结束,从而创建的线程没有机会开始执行就结束了。没有设置新建线程的分离属性,加入pthread_join后,主线程会一直等待   直到until等待的线程结束----> 那么主线程自己才会结束,使得创建的线程有机会执行。

所有的线程都有一个线程号,也就是ThreadID,类型为pthread_t,通过调用pthread_self()函数可以获得自身的线程号。

在多线程编程的时候,我们经常会以for循环的形式调用pthread_join函数,既然运行pthread_join后,主线程就阻塞了,也就没有办法调用后面的pthread_join,为啥还用for循环?

但是这个过程是这样的:

pthread_join(1,NULL)

pthread_join(2,NULL)

pthread_join(3,NULL)

pthread_join(4,NULL)

pthread_join(5,NULL)

主线程在第一个pthread_join(1,NULL)线程处就挂起了,那么在1号线程结束接着在等待2号线程,3号线程。

当然会出现3,4,5号线程比1,2先结束的情况; 那么这时候3,4,5线程结束后,3,4,5资源并没有被释放掉。因为主线程还是阻塞等待回收1,2号线程的资源,等到1,2号线程结束了,那么再3,4,5资源,然后主线程再退出。

 

========================================

转自:http://blog.csdn.net/sharelearner/article/details/11533367

 

pthread_kill:

别被名字吓到,pthread_kill可不是kill,而是向线程发送signal。还记得signal吗,大部分signal的默认动作是终止进程的运行,所以,我们才要用signal()去抓信号并加上处理函数。

int pthread_kill(pthread_t thread, int sig);

向指定ID的线程发送sig信号,如果线程代码内不做处理,则按照信号默认的行为影响整个进程,也就是说,如果你给一个线程发送了SIGQUIT,但线程却没有实现signal处理函数,则整个进程退出。

pthread_kill(threadid, SIGKILL)也一样,杀死整个进程。
如果要获得正确的行为,就需要在线程内实现signal(SIGKILL,sig_handler)了。

所以,如果int sig的参数不是0,那一定要清楚到底要干什么,而且一定要实现线程的信号处理函数,否则,就会影响整个进程。


OK,如果int sig是0呢,这是一个保留信号,一个作用是用来判断线程是不是还活着。

我们来看一下pthread_kill的返回值:
成功:0
线程不存在:ESRCH
信号不合法:EINVAL

所以,pthread_kill(threadid,0)就很有用啦。

int kill_rc = pthread_kill(thread_id,0);

if(kill_rc == ESRCH)
printf("the specified thread did not exists or already quit/n");
else if(kill_rc == EINVAL)
printf("signal is invalid/n");
else
printf("the specified thread is alive/n");

上述的代码就可以判断线程是不是还活着了。

 

版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://xufish.blogbus.com/logs/40545082.html

使用pthread_kill函数检测一个线程是否还活着的程序,在linux环境下gcc编译通过,现将代码贴在下面:

/******************************* pthread_kill.c *******************************/
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>

void *func1()/*1秒钟之后退出*/
{
    sleep(1);
    printf("线程1(ID:0x%x)退出。/n",(unsigned int)pthread_self());
    pthread_exit((void *)0);
}

void *func2()/*5秒钟之后退出*/
{
    sleep(5);
    printf("线程2(ID:0x%x)退出。/n",(unsigned int)pthread_self());
    pthread_exit((void *)0);
}

void test_pthread(pthread_t tid) /*pthread_kill的返回值:成功(0) 线程不存在(ESRCH) 信号不合法(EINVAL)*/
{
    int pthread_kill_err;
    pthread_kill_err = pthread_kill(tid,0);

    if(pthread_kill_err == ESRCH)
        printf("ID为0x%x的线程不存在或者已经退出。/n",(unsigned int)tid);
    else if(pthread_kill_err == EINVAL)
        printf("发送信号非法。/n");
    else
        printf("ID为0x%x的线程目前仍然存活。/n",(unsigned int)tid);
}

int main()
{
    int ret;
    pthread_t tid1,tid2;
    
    pthread_create(&tid1,NULL,func1,NULL);
    pthread_create(&tid2,NULL,func2,NULL);
    
    sleep(3);/*创建两个进程3秒钟之后,分别测试一下它们是否还活着*/
    
    test_pthread(tid1);/*测试ID为tid1的线程是否存在*/
    test_pthread(tid2);/*测试ID为tid2的线程是否存在*/

    exit(0);
}


编译:gcc -o pthread_kill -lpthread pthread_kill.c
运行:./pthread_kill

///////////////////////// 运行结果 /////////////////////////////
线程1(ID:0xb7e95b90)退出。
ID为0xb7e95b90的线程不存在或者已经退出。
ID为0xb7694b90的线程目前仍然存活。

 

以上是关于为什么对多线程编程这么怕?pthread,sem,mutex,process的主要内容,如果未能解决你的问题,请参考以下文章

Linux编程日常错误

Linux C++ pthread是什么缩写?(POSIX Threads)<pthread.h>

pthreads互斥与信号量

多线程的顺序执行

多线程的顺序执行

pthread-win32 扩展 sem_post_multiple