linux/unix多线程/多进程编程总结(一)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gx_1983/article/details/78151949

linux/unix多线程多进程编程总结(二)

linux/unix多线程,多进程编程是在实际工作中经常使用到的技能,在C语言或者C++语言面试的时候也经常会被问到此部分内容。
本文对linux/unix系统中的pthread相关的多进程和多线程编程的各个方面进行了总结,包括线程、进程、进程间通信及线程互斥等内容。一方面,给感兴趣的同事浏览斧正;另一方面,也是自己的一个技术笔记,方便以后回顾。

多线程

pthread_t

  1. 线程结构体,用于存储线程id。
  2. 定义在文件/usr/include/bits/pthreadtypes.h中,定义如下:
    typedef unsigned long int pthread_t;  //pthread_t是一个无符号长整型。

pthread_attr_t

  1. 线程属性结构体,通过pthread_attr_init初始化,通过pthread_attr_destroy销毁。
  2. 参考链接 https://www.cnblogs.com/zsychanpin/p/7122561.html

pthread_create

  1. pthread_create用于创建一个线程,函数原型为:
pthread_create (
    pthread_t * tid,  //线程id结构体
    const pthread_attr_t * attr, //线程属性结构体
    void * ( * start )(void *), //线程函数指针,返回是void *,参数也是void *
    void * arg); //传递给线程函数的参数
    //返回值:
    //    成功:
    //        返回0;
    //    失败:
    //        返回error number。
    //        EAGAIN: 达到了系统资源限制。
    //        EINVAL: attr设置无效。
    //        EPERM: 在attr属性中修改的设置没有权限。
  1. 通过pthread_create()创建的线程可以以以下三种方式退出:
    1. 调用pthread_exit( void * retval ),其中retval会返回给同一进程中调用了pthread_join(pthread_t thread, void **retval)的线程。即pthread_exit中的retval会被传递到void **retval指向的空间。
    2. 线程函数正常return,那么return的值也会被传递到pthread_join()中的void ** retval指向的空间。
    3. 线程被pthread_cancel(pthread_t thread)调用取消。当被取消的线程结束了(terminated),那么pthread_join()这个线程的线程的void ** retval会被设置成PTHREAD_CANCELED。

代码一:

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

void * test( void * par) {
        int loop = 5,i = 0;
        for(i = 0;i <5;i++) {
                sleep(1);
                printf("线程id是:%d\n", pthread_self()); //获得线程自身id。
        }
        return NULL;
}

int main(int argc, char ** argv) {
        pthread_t t; //线程结构体
        pthread_create(&t, NULL, test, NULL); //创建线程
        pthread_join(t,NULL); //等待线程执行结束(如果不加此句,那么新线程中test函数还没有执行结束主线程就退出了,
                              //而主线程退出会导致整个进程退出,因此如果没有此句,test()不能执行完成。)
        return 0;
}

代码二:(向线程函数传递参数)

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

typedef struct {
        int a;
        int b;
        int c;
        int d;
} A;

void * test( void * par) {
        A * p = (A *)par; //类型转换
        printf ("%d %d %d %d\n",p->a, p->b, p->c, p->d); //使用线程参数
        return NULL;
}

int main(int argc, char ** argv) {
        pthread_t t;
        A par;

        par.a = 1; par.b = 2; par.c = 3; par.d = 4; //线程参数
        pthread_create(&t, NULL, test, (void *)&par); //传递线程参数
        pthread_join(t,NULL);
        return 0;
}

pthread_join

  1. 调用pthread_join的函数会阻塞在pthread_join函数的调用处,直到pthread_join等待的线程结束,调用线程才会继续从pthread_join处执行。见代码一。
  2. 函数原型:
    int pthread_join( pthread_t thread, //线程id。 
                      void ** retval ); //存储线程返回值的空间的地址。
    //返回值:
    //    成功返回0,
    //    失败返回error number.
    //        EDEADLK: 检测到死锁(两个线程互相锁定或者thread参数是本线程。即线程自己不能join自己,否则返回EDEADLK错误。)
    //        EINVAL: thread参数无效或者另外一个线程正在等待join这个thread。
    //        ESRCH: 查不到线程Id。
  1. pthread_join的操作里包括了pthread_detach的行为,调用pthread_join会自动分离线程。
  2. 以下代码验证:
    1. pthread_join如何获得线程退出状态。
    2. 验证对于同一个线程不能同时多次pthread_join,否则程序崩溃。
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
//对于同一个线程不能同时多次pthread_join,否则程序崩溃。
typedef struct {
        int a;
        int b;
} RET;

void * test(void * par) {
        RET * p = malloc(sizeof(RET));
        sleep(5);
        p->a = 10;
        p->b = 20;
        pthread_exit((void *)p);  //线程退出,并且返回退出状态。
}

void * test1(void * par) {
        pthread_t t = *((pthread_t *)par);
        void * pRet = NULL; //用于存储线程退出状态指针
        printf("In test1, tid is %ld\n", t);
        pthread_join(t, &pRet); //获得线程t的线程退出状态。
        printf("Here.\n");
        printf("Func test1. ret is %d, %d\n", ((RET *)pRet)->a, ((RET *)pRet)->b); //打印线程退出返回的值。
        return NULL;
}

int main(int argc, char ** argv) {
        pthread_t t, t1;
        void * pRet;
        pthread_create(&t, NULL, test, NULL); //创建运行test的线程。
        printf("In main t tid is %ld\n", t);
        pthread_create(&t1, NULL, test1, &t); //创建运行test1的线程。
        pthread_join(t,&pRet); //此处join了t线程,在函数test1中也join了t线程。
        pthread_join(t1, NULL); 
        printf("ret is %d, %d\n", ((RET *)pRet)->a, ((RET *)pRet)->b);
        return 0;
}
上面程序运行时发生崩溃,原因是对于线程t执行了两次pthread_join函数。
[root@localhost pthread]# ./a.out 
In main t tid is 139668474418944
In test1, tid is 139668474418944
Here.
Segmentation fault (core dumped)

通过man pthread_join知道不能在多个线程中对同时对一个线程多次pthread_join.
man pthread_join结果如下:

DESCRIPTION
       The  pthread_join()  function  waits  for  the thread specified by thread to terminate.  If that thread has already terminated, then pthread_join()
       returns immediately.  The thread specified by thread must be joinable.
       (如果被join的线程已经结束,那么phread_join立即返回。)

       If retval is not NULL, then pthread_join() copies the exit status of the target thread  (i.e.,  the  value  that  the  target  thread  supplied  to
       pthread_exit(3)) into the location pointed to by *retval.  If the target thread was canceled, then PTHREAD_CANCELED is placed in *retval.

       If  multiple threads simultaneously try to join with the same thread, the results are undefined.  If the thread calling pthread_join() is canceled,
       then the target thread will remain joinable (i.e., it will not be detached).
       (如果多个线程尝试同时pthread_join同一个线程,那么结果是未定义的。但是如果调用pthread_join()的线程退出了,那么被join的线程仍然是joinable的,即可以被再次pthread_join。)

RETURN VALUE
       On success, pthread_join() returns 0; on error, it returns an error number.

ERRORS
       EDEADLK
              A deadlock was detected (e.g., two threads tried to join with each other); or thread specifies the calling thread.

       EINVAL thread is not a joinable thread.

       EINVAL Another thread is already waiting to join with this thread.

       ESRCH  No thread with the ID thread could be found.
    (此处说明了调用pthread_join最好检查返回值,至少能够在一定程度上避免死锁的发生。)

NOTES
       After a successful call to pthread_join(), the caller is guaranteed that the target thread has terminated.

       Joining with a thread that has previously been joined results in undefined behavior.
    (join一个之前已经被joined线程会导致未定义的行为。)
       Failure to join with a thread that is joinable (i.e., one that is not detached), produces a "zombie thread".  Avoid doing this, since  each  zombie
       thread  consumes  some  system  resources, and when enough zombie threads have accumulated, it will no longer be possible to create new threads (or
       processes). (如果没有对线程调用pthread_join,那么会导致一个僵尸线程的出现,这样会占据系统资源,只有在进程退出的时候这个僵尸线程才被释放。)

       There is no pthreads analog of waitpid(-1, &status, 0), that is, "join with any terminated thread".  If you believe you  need  this  functionality,
       you probably need to rethink your application design.

       All of the threads in a process are peers: any thread can join with any other thread in the process.
       (所有线程,包括主线程都是对等的,也就是说:任何线程都可以join所在进程的其他线程。)

如何修改上面崩溃了的程序?(方法:对同一个线程只调用一次pthread_join,要么去掉test1中的pthread_join,要么去掉main函数中的pthread_join。)
正确的程序如下:

#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
//获得线程返回值

typedef struct {
        int a;
        int b;
} RET;

void * test(void * par) {
        RET * p = malloc(sizeof(RET));
        sleep(5);
        p->a = 10;
        p->b = 20;
        pthread_exit((void *)p);
}

void * test1(void * par) {
    //等待test()所在线程退出,并获取test()所在线程的返回值。
        pthread_t t = *((pthread_t *)par);
        void * pRet = NULL;
        printf("In test1, tid is %ld\n", t);
        pthread_join(t, &pRet); //对t只pthread_join了一次。
        printf("Here.\n");
        printf("Func test1. ret is %d, %d\n", ((RET *)pRet)->a, ((RET *)pRet)->b);
        return NULL;
}

int main(int argc, char ** argv) {
        pthread_t t, t1;
        pthread_create(&t, NULL, test, NULL);
        printf("In main t tid is %ld\n", t);
        pthread_create(&t1, NULL, test1, &t);
        pthread_join(t1, NULL);
        return 0;
}

- 线程的joinable和detached状态。

  • 一个线程在生命周期中会在多个状态之间进行转换,joinable和detached是线程的两种状态,joinable可理解为“可汇合的”的意思(即线程最终要和主线程汇合),detached理解为“分离的”状态。
  • 一个进程启动之后会默认创建一个线程,这个线程叫做“主线程”,主线程接下来可能会再次创建多个线程,这些线程与主线程并行执行,主线创建的n个线程之间的关系是对等的,而主线程与从线程之间具有一定的管控关系(主线程结束则所有线程结束,整个进程结束。)。因为主线程与其他线程之间的这个特殊关系,一般在多线程程序中,都会在主线程中调用pthread_join()来汇合(连接)它创建的子线程,以回收从线程的资源。pthread_join()函数会阻塞主线程,直到其join的线程结束,pthread_join()才会继续执行,这样就避免了主线程先于从线程退出的问题。
  • 线程执行结束之后系统并不会回收其占用的系统资源(例如线程id),所以如果不对joinable线程执行pthread_join,那么另外一个隐患就是这些系统资源需要等到进程整个退出的时候才被释放,这样在多线程程序中就可能会引起资源不足问题(例如线程id达到最大限制),所以对于joinable的线程(线程默认都是joinable的)需要调用pthread_join()。
  • pthread_detach()函数可以对一个线程进行分离(意思就是与主线程分离,即脱离了主线程的管控),分离之后的线程处于detached状态,detached的线程在结束之后其占用的资源(例如线程句柄等)能够立即被系统回收,而不需要pthread_join()来回收线程资源了。(pthread_detach()可以使得线程资源在线程结束之后立即被系统回收。)
  • 缺省情况下线程都是joinable的,joinable可以通过pthread_detached()来进行分离,而分离了的线程不能再次被变成joinable的了。似的线程变为detached可以通过pthread_detach(),也可以在线程创建的时候设置线程属性PTHREAD_CREATE_DETACHED使得线程创建出来就是detached状态的。
  • 创建出来的线程也可以通过pthread_detach(pthread_self())来detach自己。
  • detach线程这种方式常用在线程数较多的情况,有时让主线程逐个等待子线程结束,或者让主线程安排每个子线程结束的等待顺序,是很困难或者不可能的。所以在并发子线程较多的情况下,这种detach线程的方式也会经常使用。
  • 参考连接 http://blog.csdn.net/k569462166/article/details/50579423

pthread_detach

  1. 函数原型: int pthread_detach(pthread_t thread);
  2. 函数说明:
    1. 参数thread: 要从进程中分离(detach)中去的线程。
    2. 创建一个线程的默认状态是joinable的,如果一个线程结束但是没有被join,则它就成为了僵尸线程(一直占用系统资源,但是线程已经不存在了,不做任何事情。直到进程结束资源才被回收。)。
    3. 在调用了pthread_join之后,如果线程已经结束了,那么pthread_join会立即返回,如果线程还没有结束,那么调用pthread_join的线程会被阻塞。但是在有些时候我们并不希望调用pthread_join的线程被阻塞。在不希望线程被阻塞的场景,我们不能调用pthread_join,我们应该调用pthread_detach分离线程(pthread_detach告诉操作系统此线程已经从进程中分离出去了(此分离只是概念上的分离,变量等等并没有真实的分离,线程还是属于进程中的线程,只是在操作系统中做了标识,这个线程结束之后线程占用的资源(线程堆栈等)可以直接被操作系统回收。))。
    4. 线程可以分离自己,也可以由别的线程分离。
      1. 线程分离自己:pthread_detach(pthread_self());
      2. 线程分离其他线程: pthread_detach( thread_id );
      3. 线程被分离后就不需要再被pthread_join了,线程结束之后操作系统就会自动回收线程的资源。
    5. 分离一个正在运行的线程不会对线程带来任何影响,仅仅是通知操作系统当前线程结束的时候其所属的资源可以被回收。没有被分离的线程终止的时候会保留其虚拟内存,包括他们的堆栈和其他的系统资源。分离线程意味着告诉操作系统不再需要此资源,允许系统将分配给他们的资源回收。线程可以分离自己,任何获得其ID的其他线程可以随时分离它。

pthread_exit

  1. 从当前线程退出,并返回参数中的值给所有。
  2. 函数原型: int pthread_exit(void * value_ptr);
  3. 函数说明:
    1. 用来终止当前线程,并通过指针value_ptr返回程序状态给调用pthread_join()函数等待此线程结束的线程(对同一个线程只能同时又一次有效的pthread_join,见pthread_join一节)。
    2. 可以在main函数中调用pthread_exit(0),这样主线程(即进程)就必须等到所有线程结束才能终止。
      代码如下:
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
//获得线程返回值

typedef struct {
        int a;
        int b;
} RET;

void * test(void * par) {
        RET * p = malloc(sizeof(RET));
        sleep(5);
        p->a = 10;
        p->b = 20;
        pthread_exit((void *)p);
}

void * test1(void * par) {
        pthread_t t = *((pthread_t *)par);
        void * pRet = NULL;
        printf("In test1, tid is %ld\n", t);
        pthread_join(t, &pRet);
        printf("Here.\n");
        printf("Func test1. ret is %d, %d\n", ((RET *)pRet)->a, ((RET *)pRet)->b);
        return NULL;
}

int main(int argc, char ** argv) {
        pthread_t t, t1;
        pthread_create(&t, NULL, test, NULL);
        pthread_create(&t1, NULL, test1, &t);
        //pthread_exit(0);
        return 0;
}
注意:要么在主线程中pthread_join,要么pthread_exit(0),否则当主线程执行完成,即使别的线程正在执行,那么整个进程也会结束。如上例,如果不加pthread_exit(0),则不会有任何打印;如果加上pthread_exit(0),则程序正确执行。
[root@localhost pthread]# ./a.out 
In test1, tid is 140269881673472
Here.
Func test1. ret is 10, 20

pthread_self

获得当前线程的id号,见代码一。
函数原型 pthread_t pthread_self( void );

pthread_equal

函数原型: int pthread_equal(pthread t1, pthread t2);
函数说明:
    判断两个线程的id是否相等,即传递过来的两个线程是否是同一线程。
    t1 == t2,返回非0;
    t1 != t2,返回0。

验证pthread_equal:

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

void * test(void * par) {
        sleep(1);
        return NULL;
}

int main(int argc, char ** argv) {
        pthread_t t;
        pthread_create(&t, NULL, test, NULL);
        //if(pthread_equal(t,t) == 0) {
        if(pthread_equal(t, pthread_self()) == 0) {
                printf("threads are different: %ld %ld\n", t, pthread_self());
        } else {
                printf("threads are same: %ld %ld\n", t, pthread_self());
        }
        return 0;
}

pthread_attr_init/pthread_attr_destroy

  • 初始化线程属性结构体,初始化后线程属性值都是系统默认的线程属性,如果不对线程属性进行初始化,那么属性值不定,所以在使用的时候,如果要设置线程属性,都要执行pthread_attr_init()把不需要修改的线程属性设置为默认值。
  • 函数原型:
    • int pthread_attr_init(pthread_attr_t *attr); //初始化pthread_attr_t
    • int pthread_attr_destroy(pthread_attr_t *attr); //销毁pthread_attr_t
  • man手册说明:
    Calling pthread_attr_init() on a thread attributes object that has already been initialized results in undefined behavior. (对于已经初始化了的pthread_attr_t不应该再次被调用pthread_attr_init,否则会导致未定义行为,即手册建议最好只调用一次。)

    When a thread attributes object is no longer required, it should be destroyed using the pthread_attr_destroy() function. Destroying a thread attributes object has no effect on threads that were created using that object. (对pthread_attr_t结构体的销毁不会影响用这个属性已经创建完成的线程。)

    Once a thread attributes object has been destroyed, it can be reinitialized using pthread_attr_init(). Any other use of a destroyed thread attributes object has undefined results. (pthread_attr_t被销毁之后可以再次调用pthread_attr_init进行初始化,不初始化就使用已经销毁了的attr会导致未定义行为。)

pthread_attr_setdetachstate/pthread_attr_getdetachstate

  • 获得及设置线程的detach属性。
  • 函数原型:
    • int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
    • int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);
  • 两个函数都是线程安全函数(man手册)

pthread_cancel

  • 用于cancel一个线程,线程是否能够被取消及何时被取消决定于线程的参数设定,具体参看以下man手册。
  • pthread_cancel执行结束不代表被取消的线程已经终止了,应该通过pthread_join来获得被取消线程的状态即返回值。
DESCRIPTION
       The  pthread_cancel()  function  sends  a cancellation request to the thread thread.  Whether and when the target thread reacts to the cancellation
       request depends on two attributes that are under the control of that thread: its cancelability state and type.

       A thread's cancelability state, determined by pthread_setcancelstate(3), can be enabled (the default for new threads) or disabled.  If a thread has
       disabled  cancellation,  then  a  cancellation request remains queued until the thread enables cancellation.  If a thread has enabled cancellation,
       then its cancelability type determines when cancellation occurs.

       A thread's cancellation type, determined by pthread_setcanceltype(3), may be either asynchronous or deferred (the default for new threads).   Asyn‐
       chronous  cancelability  means that the thread can be canceled at any time (usually immediately, but the system does not guarantee this).  Deferred
       cancelability means that cancellation will be delayed until the thread next calls a function that is a cancellation point.   A  list  of  functions
       that are or may be cancellation points is provided in pthreads(7).

       When a cancellation requested is acted on, the following steps occur for thread (in this order):

       1. Cancellation clean-up handlers are popped (in the reverse of the order in which they were pushed) and called.  (See pthread_cleanup_push(3).)

       2. Thread-specific data destructors are called, in an unspecified order.  (See pthread_key_create(3).)

       3. The thread is terminated.  (See pthread_exit(3).)

       The  above  steps  happen asynchronously with respect to the pthread_cancel() call; the return status of pthread_cancel() merely informs the caller
       whether the cancellation request was successfully queued.

       After a canceled thread has terminated, a join with that thread using pthread_join(3) obtains PTHREAD_CANCELED as the thread's exit status.  (Join‐
       ing with a thread is the only way to know that cancellation has completed.)

线程间并发

互斥锁mutex

  1. 互斥锁一般都用在同一个进程内线程之间的互斥。通过修改mutex的共享属性pthread_mutexattr_setpshared()也可以实现互斥锁在多个进程之间共享,但是在实际使用中,这种用法并不普遍。原因如下:
    • 要想互斥锁用于进程之间,那么要把互斥锁放在共享内存中,并且进行初始化,这就涉及到一个问题:不能多个进程都对互斥锁进行初始化,因为这样的话由于调度的关系,有可能有的进程完成了初始化后开始加锁操作,而另外一个进程正在初始化互斥锁,从而导致问题发生。为了避免这个问题就需要保证在锁定互斥锁的时候互斥锁已经被初始化了,这就需要采用特定的程序运行模式,比如说主进程先初始化互斥锁,然后再启动其他进程;或者在互斥锁初始化的过程中采用另外一种进程同步方式保证锁一次且仅一次进行了初始化。从这个过程来看,采用进程间共享的互斥锁并不比其他进程间协作方式方便,在实际使用中当然是用最方便的方式,所以进程间的互斥锁在实际使用中并不是很普遍。参考连接 https://www.cnblogs.com/my_life/articles/4538299.html
    • 另外一个问题是持有锁的进程发生了死锁,从上面的链接了解到,“实测发现,down 了以后进程退出,互斥锁仍然是被锁定的。”,上文链接也提出了一种解决方法,这种解决方法是可行的。
    • 因此,最后的结论时,进程间互斥锁是可行的,但是使用的时候要注意以上事项,没有其他进程间通信方法来的方便。

pthread_mutexattr_t

  1. 互斥锁属性。参考链接 http://lobert.iteye.com/blog/1762844

pthread_mutex_t

  1. 互斥锁结构体.
  2. 静态初始化:
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  3. 动态初始化:
    pthread_mutex_t mutex;
    pthread_mutex_init(&mutex, NULL);

pthread_mutex_init

  1. 互斥锁初始化函数:
    int pthread_mutex_init( pthread_mutex_t * mutex, pthread_mutexattr_t * attr);

pthread_mutex_lock

  1. 函数原型: int pthread_mutex_lock(pthread_mutex_t * mutex);

pthread_mutex_unlock

  1. 函数原型:int pthread_mutex_unlock(pthread_mutex_t * mutex);

pthread_mutex_trylock

  1. 函数原型: int pthread_mutex_trylock(pthread_mutex_t * mutex);
  2. 当互斥量已经被锁住时该函数将返回错误代码EBUSY。
  3. 使用非阻塞函数加锁时,需要确保只有在获得锁成功后才能执行解锁操作。只有拥有互斥量的线程才能解锁它。
  4. 使用时需要判断 != EBUSY,意味着加锁成功,再判断==0,意味着没有别的错误发生,然后再执行功能代码。注意只有加锁成功后才能释放信号量,没有加锁成功不能执行释放信号量的操作。

pthread_mutex_destory

  1. 函数原型: int pthread_mutex_destroy(pthread_mutex_t mutex);
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>

int count = 0;
pthread_mutex_t mutex;
//pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; /静态初始化互斥量

void * test( void * par ) {
        int i = 0;
        for(i = 0;i < 10; i++) {
                sleep(2);
                pthread_mutex_lock(&mutex); //互斥量加锁
                count += 1;
                printf("%d\n", count);
                pthread_mutex_unlock(&mutex); //互斥量解锁:
        }
}

void * test1(void * par) {
        int ret = 0, i = 0;
        for(i = 0; i < 10;i++) {
                ret = pthread_mutex_trylock(&mutex); //尝试加锁
                if(ret == EBUSY) {  //如果锁被占用,那么立即返回EBUSY
                        printf("Mutex EBUSY.\n");
                } else if(ret == 0) { //表示加锁成功
                        printf("Try lock successfully.\n");
                        count++;
                        pthread_mutex_unlock(&mutex); //只有在加锁成功之后才能释放互斥锁
                }
                sleep(1);
        }
        return NULL;
}

int main(int argc, char ** argv) {
        pthread_t t1, t2, t3, t4;
        pthread_mutex_init(&mutex, NULL); //互斥量初始化
        pthread_create(&t1, NULL, test, NULL);
        pthread_create(&t2, NULL, test, NULL);
        pthread_create(&t4, NULL, test1, NULL);
        pthread_create(&t3, NULL, test, NULL);
        pthread_join(t1, NULL);
        pthread_join(t2, NULL);
        pthread_join(t3, NULL);
        pthread_join(t4, NULL);
        pthread_mutex_destroy(&mutex); //互斥量销毁
}

条件变量

  1. 代码示例1
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

typedef struct {
    pthread_mutex_t _mutex;
    pthread_cond_t _cond;
} A;

A a = {PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER};

void * producer(void * par) {
    sleep(3);
    for(int i = 0; i < 1; i++) {
        sleep(1);
        printf("Sending info.\n");
        pthread_mutex_lock(&(a._mutex));   //先加锁再wait。
        //pthread_cond_signal(&(a._cond));  //signal的话,只有一个wait的线程会执行。
        pthread_cond_broadcast(&(a._cond)); //broadcast的话,两个wait的线程都会执行。
        pthread_mutex_unlock(&(a._mutex));
    }
    printf("producer exit.");
    return NULL;
}
void * consumer(void * par) {
    pthread_mutex_lock(&(a._mutex));  //先加锁再wait.
    pthread_cond_wait(&(a._cond), &a._mutex);
    pthread_mutex_unlock(&(a._mutex));
    printf("Consumer wake up. %ld\n", pthread_self());
    return NULL;
}

int main(int argc, char ** argv) {
    pthread_t t1,t2,t3;
    pthread_create(&t1, NULL, consumer, NULL);
    pthread_create(&t2, NULL, producer, NULL);
    pthread_create(&t3, NULL, consumer, NULL);
    pthread_exit(0);
}

pthread_cond_t

  1. 条件变量是用来共享数据状态信息的。条件变量的作用是发信号,而不是互斥。条件变量不提供互斥,需要一个互斥量来提供对共享数据的访问,这就是为什么在等待一个条件变量时必须指定一个互斥量。
  2. 动态初始化的条件变量需要调用pthread_cond_destroy来释放。而静态初始化的条件变量不需要调用pthread_cond_destroy来释放。
  3. 条件变量提供两种方式来唤醒等待条件变量的线程。一个是信号,一个是广播。发送信号只会唤醒一个等待线程。而广播会唤醒所有等待该条件变量的线程(所有等待的线程都会执行)
  4. 总是在while循环等待一个条件变量是一个好的习惯。

pthread_cond_wait

  1. 等待条件变量
  2. 函数原型:
    int pthread_cond_wait(pthread_cond_t * cond, pthread_mutex_t * mutex);
    在阻塞执行pthread_cond_wait的线程之前,pthread_cond_wait将解锁互斥量,这样信号及广播操作才能够加锁互斥量,然后发送信号。在pthread_cond_wait获得信号之后,pthread_cond_wait会再次加锁互斥量,然后在线程中执行互斥操作。
    pthread_cond_wait(&cond);函数在把线程放入等待队列之后会把cond对应的互斥锁进行解锁,这样其他的线程才能加锁互斥锁,然后进行相应的操作。在其他线程给条件变量发送了信号或者广播之后会导致pthread_cond_wait函数退出,但是在退出之前pthread_cond_wait函数会重新把互斥锁进行加锁操作,这样在线程后续的操作过程中互斥量是被锁住的。

pthread_cond_signal

  1. 给条件变量发送信号
  2. 函数原型:
    int pthread_cond_signal( pthread_cond_t * cond );

pthread_cond_broadcast

  1. 广播条件变量
  2. 函数原型:
    int pthread_cond_broadcast(pthread_cond_t * cond);

pthread_cond_timewait

  • 等待条件变量,定时超时。
  • 函数原型:
    int pthread_cond_timewait(
    pthread_cond_t * cond, //条件变量
    pthread_mutex_t * mutex, //互斥量
    struct timespec * expiration ); //超时时间结构体

pthread_cond_init

  • 静态初始化
    pthread_cond_t condition = PTHREAD_COND_INITIALIZER; //不需要调用pthread_cond_destroy()释放。
  • 动态初始化
    pthread_cond_t cond;
    pthread_cond_init(&cond, NULL); //需要调用pthread_cond_destroy()销毁。
    函数原型: int pthread_cond_init( pthread_cond_t * cond, pthread_condattr_t * condattr );
    cond: 条件变量指针。
    condattr: 条件变量的属性。

pthread_cond_destroy

  • 销毁条件变量。
  • 函数原型:
    int pthread_cond_destroy( pthread_cond_t * cond );

信号量

  • 信号灯遵循传统的unix形式的报错机制,如果函数发生错误则返回-1,并且错误号存储在errno中。
  • 头文件 semaphore.h
#include <iostream>
#include <semaphore.h>
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

using namespace std;
//信号量的值可以大于1,例如为10,sem_wait会把信号量减1,sem_post会把信号量加1.
//信号量的所有函数都是失败返回-1,对应错误代码存储在errno里。
sem_t sem;

void * fun1(void * par) {
        while(1) {
                sem_wait(&sem); //减1,如果到0则等待。
                cout << "Wait tid: " << pthread_self() << endl;
        }
        return NULL;
}

void * fun2(void * par) {
        while(1) {
                sleep(2);
                sem_post(&sem); //信号量加1.
                sem_post(&sem); //信号量加1.
                sem_post(&sem); //信号量加1.
                cout << "Post tid: " << pthread_self() << endl;
        }
        return NULL;
}

int main(int argc, char ** argv) {
        sem_init(&sem, 0, 5); //初始化信号量。
        pthread_t t1,t2,t3;
        pthread_create(&t1, NULL, fun1, NULL);
        pthread_create(&t2, NULL, fun1, NULL);
        pthread_create(&t3, NULL, fun2, NULL);
        pthread_exit(0);
        return 0;
}

sem_init

  • 信号灯初始化函数,函数原型为:
  • int sem_init(sem_t * sem, int pshared, unsigned int value);
  • pshared为非0,表示信号灯可以在进程间共享。pshared为0,表示信号灯只能在同一个进程的线程之间共享。
  • (在进程间共享的信号灯是否要创建在共享内存上? 是。)
  • sem表示信号灯指针,value为要给信号等赋的值。
  • man 说明
sem_init() initializes the unnamed semaphore at the address pointed to by sem. The value argument specifies the initial value for the semaphore.
The pshared argument indicates whether this semaphore is to be shared between the threads of a process, or between processes. (pshared参数表示信号量是否可以在进程之间共享。)
If pshared has the value 0, then the semaphore is shared between the threads of a process, and should be located at some address that is visible to all threads (e.g., a global variable, or a variable allocated dynamically on the heap).
If pshared is nonzero, then the semaphore is shared between processes, and should be located in a region of shared memory (see shm_open(3), mmap(2), and shmget(2)). (Since a child created by fork(2) inherits its parent's memory mappings, it can also access the semaphore.) Any process that can access the shared memory region can operate on the semaphore using sem_post(3), sem_wait(3), etc. (如果pshared设置为非0,那么信号量可以在进程之间共享,并且如果想要在进程之间共享该信号量的话应该把信号量放置在共享内存区。)
Initializing a semaphore that has already been initialized results in undefined behavior.
(一个信号量不能被初始化两次。)

sem_destroy

  • 销毁信号灯。
  • int sem_destroy(sem_t * sem);
  • 如果销毁成功返回0,在销毁时如果还有其他线程在占用信号灯,则返回EBUSY,此时destroy函数会被阻塞。

sem_wait

  • 等待信号灯。
  • int sem_wait(sem_t * sem);
  • 信号灯值大于0的时候,sem_wait会把该值减1;如果信号灯值等于0,则线程会被阻塞,直到信号灯的值能够成功被降低。
  • 当信号灯初始化为1的时候,是一个锁定操作;当初始化为0的时候,是一个等待操作。
  • 信号灯可以初始化为大于1的值。

sem_trywait

  • 尝试加锁一个信号灯,如果trywait的时候信号灯大于0,则把信号灯的值减去1;如果trywait的时候信号灯已经为 0,则返回EAGAIN。

sem_post

  • int sem_post(sem_t * sem);
  • 把信号量加1。

读写锁

  • 读写锁实际是一种特殊的自旋锁。它把对共享资源的访问者划分为读者和写者。读者只对共享资源进行读访问;写者需要对共享资源进行写操作和读操作。一个读写锁同时只能有一个写锁或者多个读锁,但是不能既有写锁又有读锁被加锁。读写锁适合于对数据结构的读次数比写次数多的情况。 读写锁又叫共享-独占锁。
  • 头文件 pthread.h
#include <iostream>
#include <pthread.h>
#include <unistd.h>

using namespace std;

pthread_rwlock_t rwlock;

void * reader(void * par) {
        while(1) {
                pthread_rwlock_rdlock(&rwlock);
                sleep(1);
                cout << "Pthread is locked: " << pthread_self() << endl;
                pthread_rwlock_unlock(&rwlock);
        }
        return NULL;
}

void * writer(void * par) {
        while(1) {
                pthread_rwlock_wrlock(&rwlock);
                cout << "Pthread is locked by writter." << endl;
                pthread_rwlock_unlock(&rwlock);
                sleep(2);
        }
        return NULL;
}

int main( int argc, char ** argv) {
        pthread_rwlock_init(&rwlock, NULL);
        pthread_t t1, t2, t3;
        pthread_create(&t1, NULL, reader, NULL);
        pthread_create(&t2, NULL, writer, NULL);
        pthread_create(&t3, NULL, reader, NULL);
        pthread_exit(0);
        return 0;
}

pthread_rwlock_init

  • int pthread_rwlock_init(pthread_rwlock_t * rwlock, const pthread_rwlockattr_t * attr);
  • 成功返回0,失败返回错误代码。
  • 不能多次对同一个读写锁执行初始化操作,行为未定。

pthread_rwlock_rdlock

  • int pthread_rwlock_rdlock( pthread_rwlock_t * rwlock); 读加锁

pthread_rwlock_tryrdlock

  • int pthread_rwlock_tryrdlock(pthread_rwlock_t * rwlock); 成功返回0,失败返回EBUSY。

pthread_rwlock_wrlock

  • int pthread_rwlock_wrlock(pthread_rwlock_t * rwlock); 写加锁

pthread_rwlock_trywrlock

  • int pthread_rwlock_trywrlock(pthread_rwlock_t * rwlock); 成功返回0, 失败返回EBUSY。

pthread_rwlock_destroy

  • int pthread_rwlock_destroy(pthread_rwlock_t * rwlock); 销毁读写锁

pthread_rwlock_unlock

  • int pthread_rwlock_unlock(pthread_rwlock_t * rwlock); 解除锁定。

读写锁属性设置相关函数

pthread_rwlockattr_t

  • 读写锁属性结构体

pthread_rwlockattr_init/pthread_rwlockattr_destroy

  • 初始化/销毁读写锁属性对象。
  • 不能初始化一个已经被初始化了的读写锁属性对象。

pthread_rwlockattr_getpshared/pthread_rwlockattr_setpshared

  • 获得读写锁的进程间共享属性。如果在进程间共享需要设置 PTHREAD_PROCESS_SHARED。

猜你喜欢

转载自blog.csdn.net/gx_1983/article/details/78151949
今日推荐