您的位置:首页 > 产品设计 > UI/UE

Apue学习:线程

2016-04-03 23:21 375 查看

线程标识

#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多线程程序设计
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: