Apue学习:线程

来源:互联网 发布:网络大专可以做人事吗 编辑:程序博客网 时间:2024/05/17 09:31

线程标识

#include <pthread.h>int pthread_equal(pthread_t tid1, pthread_t tid2);pthread_t pthread_self(void);

线程创建

Unix中进程起始时只有一个master threads,除非使用pthread_create来创建更多的线程。

#include <pthread.h>int phread_create(pthread_t *restrict tidp,                  const pthread_attr_t *restrict attr,                  void *(*start_rtn)(void *),                  void *restrict arg);                  //成功返回0,出错返回错误编码

说明:
注意tidp保存的是线程标识符,线程的属性有attr指定,或者用NULL表示使用默认属性,线程创建后从start_rtn开始执行,start_rtn只有一个void *参数,用arg表示。

注意:

  • 当一个线程创建之后,线程有权限使用进程空间,以及会继承calling thread的floating-point
    environment以及signal mask,但是pending
    signals会被清除。即,如果在A线程中创建B线程,那么B线程会继承A线程的signal mask,但是B线程的peding
    signal会被清空。
    pthread的出错方式是返回一个错误代码,并不是设置环境变量errno
  • 使用pthread_create时的注意事项:
    并不是在pthread_create返回之后,新线程才去运行,而是一旦新线程创建完毕,那么就是新线程与其他线程就会去进程cpu,谁拿到了,谁就去执行。而在pthread_create函数体类,新线程就别创建完毕了,这样新线程就可能会去执行,而此时ntid却还没有别初始化完毕,所以在新线程中我们使用了pthread_self来获取新新线程的id,而没有用tid。

线程终止

终止方式

线程终止的方式主要有3种:
1.从线程中return
2.线程被其他线程取消
3.线程中调用pthrea_exit

另外需要注意的是,在任意一个进程中的线程里面调用eixt(),_exit()都会造成该进程的终止。

pthread_exit函数与pthread_join函数

#include <pthread.h>void pthread_exit(void *rval_ptr);int pthread_join(pthread_t thread, void **rval_ptr);

说明:
pthread_exit与pthrea_join

  • 1.如果在A线程中创建了B线程,B线程没有被分离,那么B线程就相当于A线程的子线程,在B线程调用pthread_exit()后,A线程可以调用pthread_join函数来获得B线程的一些情况。
  • 2.如果B线程被分离出去,那么A线程调用pthread_join就会返回EINVAL。
  • 3.如果线程是被取消的,那么pthread_join中的rval_ptr所指向的内存地址所指向的内容就被设置为PTHREAD_CANCELED.
  • 4.pthread_join会阻塞线程,直到指定线程退出

注意点:

  • 注意的是pthread_exit(void *rval_ptr),这个值是一个rval_ptr,在线程调用这个函数后,rval_ptr就会指向一个内存地址。 而在另一个线程中调用pthread_join(pthrea_d thtrea, void
    **rval_ptr),rval_ptr所指向的就是rval_ptr指向的内容。 所以若果一个线程在退出后想要返回一些信息,那么这个信息最好不要在线程的栈空间中保留,最好使用malloc把信息保存在堆中,这样这些信息就不会随着线程的退出而无效。
  • 另外需要注意的是一个线程调用pthread_exit(),那么线程退出后,就会调用线程的退出清理函数,类似于进程的atexit()所注册的函数。而在线程的启动例程中调用return就不会调用线程的退出清理函数。

pthread_cancel函数

int pthread_cancel(pthread_t tid)

表明我这个线程想要取消tid这个线程。注意只是表示我想要取消,我并不会等到线程真正取消。

pthread_cleanup_push/pop

void pthread_cleanup_push(void (*rtn)(void *), void *arg);void pthread_cleanup_pop(int execute);

说明:
作用类似于atexit()
注意:
rtn函数只有在3中情况下会执行:
1.线程调用pthread_exit
2.线程响应别的线程的取消操作
3.使用pthread_cleanup_pop,并且pexecute的参数非0
4.push 与 pop要成对出现

解释:
1.如果execute参数为0,那么就pthread_cleanup_pop函数就什么都不做。
2.如果线程是从主例程中使用return返回,那么就不会执行rtn。主例程指的是创建时使用的start_fun。所以想要执行清理函数要使用pthread_exit。

注意点:
默认情况下,线程的终止状态会保持到我们调用pthread_join。但是如果线程是处于分离状态,那么线程的终止状态会被立即取回。pthread_join并不能作用于一个处于分离状态的线程,这样做会导致pthread_join返回错误。

线程同步

1.互斥量

互斥锁:
互斥锁指的是当我们访问共享内容时,我们先上个锁,在访问结束之后再解开锁。如果我们访问共享内容时,这个共享内容已经被上锁了,我们就会被阻塞起来,直到有个线程释放了这个锁,并且唤醒的是我们。

#include <pthread.h>int pthread_mutex_init(pthread *restrict mutex,                       const pthread_mutexattr_t *restrict attr);int pthread_mutex_destroy(phtread_mutex_t *mutex);int pthread_mutex_lock(pthread_mutex_t *mutex);int pthread_mutex_trylock(pthread_mutex_t *mutex);int phtread_mutex_unlock(pthread_mutex_t *mutex);

互斥锁的操作,注意trylock不会造成阻塞,而lock会造成阻塞。

2.避免死锁

解决死锁的思路:
1.资源有序加锁
2.使用pthread_mutex_trylock,成功的时候使用lock,不成功就放弃自己已经获得的锁,然后等一会之后重新开始加锁。

pthread_mutex_timedlock

类似于lock,但是指定了我们可以锁住的时间是多少,到时间就会返回ETIMEOUT并且释放自己的锁。

4.读写锁

说明:
读写锁:
一共有三种状态:
1.写锁状态
2.读锁状态
3.没有锁状态

写锁状态下,要想再加锁就会被阻塞住,无论你是想加读锁,还是想加写锁。在读锁状态下,你可以加读锁,并且不会被阻塞,但是如果你想加写锁,你就会被锁住。
注意如果在读锁模式下,你加了写锁,只能等待前面加上读锁的线程都释放自己的锁之后,才可以加上写锁。但是你表明自己想要加写锁之后,后面的线程想加读锁的话就会被阻塞住。

读写锁适合那些读操作频繁的程序。

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);int phtread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

类似于互斥量的try。
注意当锁是可以获得的时候,返回0。否则返回错误代码EBUSY。

5.条件变量

#include <pthread.h>int pthread_cond_init(pthread_cond_t *restrict cond,                      pthread_condattr_t *restrict attr);int pthread_cond_destroy(pthread_cond_t *cond);int pthread_cond_wait(pthread_cont_t *restrict cond,                      pthread_mutex_t *restrict mutex,                      const struct timespec *restrict timeout);int pthread_cond_signal(pthread_cond_t *cond);int pthread_cond_broadcast(pthread_cond_t *cond);

说明:

  • 条件变量要用mutex来保护。
    使用过程是:一个线程首先锁定互斥量mutex,然后调用pthread_cond_wait(),这个函数有一个原子操作,即先解锁,再把本线程挂起到等待队列中。(之所以是先解锁,是因为挂起之后就不能占用cpu操作了。)注意到为什么是原子操作,在于我们先解锁了,所以防止解锁后,条件有改变,而我们却没有捕获到这个改变,所以要用原子操作。线程被阻塞后就要等待别人的线程让条件为真然后唤醒本线程。
    这个函数返回之后,mutex会再次被锁住。
  • 可以使用signal和broadcast唤醒等待条件变量cond的线程。 signal唤醒至少一个。 broadcast唤醒所有。

注意点:
注意因为signal与broadcast都有可能唤起多个线程,所有可能一个线程使用了之后,造成条件的再一次不满足,这时就需要再次使用这个函数了。、

例子:

自旋锁

自旋锁会出现忙等,但是不会阻塞。

屏障

屏障允许线程等待,知道所有相关的线程到达了同一点,然后线程各自继续执行。

int pthread_barrier_init(pthread_barrier_t *restrict barrier,const pthread_barrierattr_t *restrict attr,unsigned int count);int pthread_barrier_destroy(pthread_barrier_t *barrier);

说明:
count指定了有多少个线程要达到这个屏障,才可以进行下一步工作。

pthread_barrier_wait

int pthread_barrier_wait(pthread_barrier_t *barrier);

说明:
如果一个线程调用了pthread_barrier_wait之后:
1.如果等待在这个屏障上的线程的个数还没有到达count,那么这个线程会被阻塞。
2.如果在调用之后,count个数到达了,那么所有的线程都会被唤醒。

注意:

  • 注意只有一个线程会在返回时获得的返回值为:PTHREAD_BARRIER_SERIAL_THREAD,这个线程就相等于是一个主线程来处理所有线程处理的结果。
    pthread_join就是一个屏障的简单例子
  • 另外要注意的是: 当barrier count到达了,并且所有线程都被解除阻塞,那么barrier就可以被重用了。但是barrier
    count并不可以修改,除非你调用了pthread_barrier_destroy函数,然后在使用init。

总结

线程是一个很重要的概念,他可以提供并发程度。线程可以看成是一个轻量级的进程。在程序开始运行是只有一个主线程,我们可以在线程中创建多个线程,但是注意一个进程可以创建的线程是有上线的。
另外需要注意的线程退出时的一些情况。例如什么时候会执行退出线程清理函数。在线程中调用exit()会怎么样。线程与线程之间的关系,线程与进程之间的关系。
另一部分就是线程同步问题了:主要有互斥量,读写锁,自旋锁,条件变量,屏障。注意死锁问题,已经解决办法。知道同步方法使用的情况有哪些。

另外在这章中看到了指针的另一种用法,注意指针是一个对象,他本身就是相当于一个整形值,我们只是在声明的时候表示这个对象指向的是什么类型。所以可以灵活运用void *。指针之间的比较其实比较的是这两个指针是否指向同一个地址。

参考

POSIX多线程程序设计

0 0
原创粉丝点击