线程
在单进程环境下使用多线程执行多个任务。一个进程的所有线程可以访问该进程的资源。当然也需要涉及处理一致性问题。
线程标识
进程ID pid_t数据类型表示 ,而线程用 pthread_t 数据类型表示。
不同平台对pthread_t 实现不同。
线程创建
#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
pthread_t main_tid;
void print_ids(const char *str)
{
pid_t pid; //进程id
pthread_t tid; //线程id
pid = getpid(); //获取当前进程id
tid = pthread_self(); //获取当前线程id
printf("%s pid: %u tid: %u (0x%x)/n",
str,
(unsigned int)pid,
(unsigned int)tid,
(unsigned int)tid);
}
void *func(void *arg)
{
print_ids("new thread:");
return ((void *)0);
}
int main()
{
int err;
err = pthread_create(&main_tid, NULL, func, NULL); //创建线程
if(err != 0){
printf("create thread error: %s/n",strerror(err));
return 1;
}
printf("main thread: pid: %u tid: %u (0x%x)/n",
(unsigned int)getpid(),
(unsigned int)pthread_self(),
(unsigned int)pthread_self());
print_ids("main thread:");
sleep(1);
return 0;
}
运行 gcc -Wall -o pthread_create pthread_create.c -lpthread
需要链接 pthread 库文件
注意:主线程需要休眠,不然可能会退出导致新线程退出。二是使用pthread_self()函数获取线程ID,而不是用main_tid,用可以用,但可能会出问题,因为新线程在主线程调用pthread_creat返回之前就运行的话,main_tid可能是未初始化的值。值得注意。
线程终止和线程等待
线程使用一般的 return ((void *) 1) 或者 pthread_exit((void *) 1),即退出状态码,在其他线程中可以通过 pthread_join函数获得该线程的退出码。
线程取消
pthread_cancel( ID )仅仅是提出要求,该线程并不等待。
线程可以安排退出时需要调用的函数,称为线程清理处理程序,记录程序记录在栈中,执行顺序和注册顺序相反。
线程同步
1 线程互斥锁
/* Try locking a mutex. */
int pthread_mutex_trylock (pthread_mutex_t *__mutex);
/* Lock a mutex. */
int pthread_mutex_lock (pthread_mutex_t *__mutex);
/* Unlock a mutex. */
int pthread_mutex_unlock (pthread_mutex_t *__mutex);
trylock和lock 最主要区别就是程序是否阻塞。
上述程序调用之前需要对 __mutex 进行初始化,两种初始化方式
1、静态分配 pthread_mutex_t mlock = PTHREAD_MUTEX_INITIALIZER;
2、动态分配,该方式后面要释放内存 。mutexattr表示属性,暂不考虑。
int pthread_mutex_init (pthread_mutex_t *__mutex,\
const pthread_mutexattr_t *__mutexattr);
/* Destroy a mutex. */
int pthread_mutex_destroy (pthread_mutex_t *__mutex);
避免死锁
死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
常见的死锁原因:
当同一线程对同一互斥量加锁两次也会产生死锁。
还有个函数 pthread_mutex_timedlock()可以指定超时时间,就是等了多久还没有等到锁释放就返回。
2 读写锁 (待实例理解)
也需要初始化,并且最后释放
/* Initialize read-write lock */
int pthread_rwlock_init (pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
/* Destroy read-write lock */
extern int pthread_rwlock_destroy (pthread_rwlock_t *rwlock);
返回值:成功返回0,否则返回错误代码
读写锁有三种状态,读模式下加锁,写模式下加锁和 解锁
/* 读模式下加锁 */
int pthread_rwlock_rdlock (pthread_rwlock_t *rwlock);
/* 非阻塞的读模式下加锁 */
int pthread_rwlock_tryrdlock (pthread_rwlock_t *rwlock);
/* 限时等待的读模式加锁 */
int pthread_rwlock_timedrdlock (pthread_rwlock_t *restrict rwlock,const struct timespec * restrict abstime);
/* 写模式下加锁 */
int pthread_rwlock_wrlock (pthread_rwlock_t *rwlock);
/* 非阻塞的写模式下加锁 */
int pthread_rwlock_trywrlock (pthread_rwlock_t *rwlock);
/* 限时等待的写模式加锁 */
int pthread_rwlock_timedwrlock (pthread_rwlock_t *restrict rwlock, const struct timespec *restrict abstime);
/* 解锁 */
int pthread_rwlock_unlock (pthread_rwlock_t *rwlock);
返回值:成功返回0,否则返回错误代码
3 条件变量
条件变量与互斥量一同使用时,允许线程以无竞争的方式等待特定条件发生。
条件变量与互斥量不同,互斥量是防止多线程同时访问共享的互斥变量来保护临界区。条件变量是多线程间可以通过它来告知其他线程某个状态发生了改变,让等待在这个条件变量的线程继续执行。通俗一点来讲:设置一个条件变量让线程1等待在一个临界区的前面,当其他线程给这个变量执行通知操作时,线程1才会被唤醒,继续向下执行。
条件变量必须和一个互斥锁配合,以防止多个线程同时请求 pthread_cond_wait() (或 pthread_cond_timedwait())的竞争条件。
具体为什么要和互斥锁一起使用:
互斥锁一个明显的缺点是他只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,他常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程会先被阻塞,然后解开互斥锁,等待条件变量发生变化。一旦其他的某个线程改变了条件变量,会发出一个signal通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线承间的同步。
可以总结为:条件变量用在某个线程需要在某种条件才去保护它将要操作的临界区的情况下,从而避免了线程不断轮询检查该条件是否成立而降低效率的情况,这是实现了效率提高。
在条件满足时,自动退出阻塞,再加锁进行操作。
Linux下C编程的条件变量:条件变量是线程中的东西,就是等待某一条件的发生和信号一样 以下是说明: 条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。
初始化
1、宏常量初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER
2 、函数初始化和回收
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *restrict cond)
等待和通知环境变量 (*restrict作用?)
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
关于为什么传递一个互斥锁,书上解释为:
(why)
值得注意的就是传递的时间是绝对值,如等三分钟,是当前时间加上三分钟后再转换为timespec结构。
pthread_cond_broadcast 和pthread_cond_signal函数可以通知线程条件已经满足,一个唤醒所有线程,一个至少唤醒一个。
深入理解
互斥量实质是竞争关系,多个线程操作一个资源时为了避免混乱,对操作的部分加锁,其他线程等待锁。而条件变量本质是等待关系,条件是可以自定义的,在一个线程中等待条件的成立,而另一线程中改变条件,当条件满足时线程使用broadcast和signal函数通知。关于互斥锁,也很容易理解,因为两个线程对同一全局变量(条件)访问,一个线程判断条件时需要加锁。具体解释如下:
pthread_mutex_t mutex; ///< 互斥锁
pthread_cond_t cond; ///< 条件变量
bool test_cond = false;
/// TODO 初始化mutex和cond
/// thread 1:
pthread_mutex_lock(&mutex); ///< 1
while (!test_cond)
{
pthread_cond_wait(&cond, &mutex); ///< 2,3
}
pthread_mutex_unlock(&mutex); ///< 4
RunThread1Func();
/// thread 2:
pthread_mutex_lock(&mutex); ///< 5
test_cond = true;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex); ///< 6
/// TODO 销毁mutex和cond
(1)条件变量的使用过程中,最为关键的一点是互斥锁的使用。细心的朋友应该发现了,我在上面的例子中标了1、2、3、4、5、6个标号。在这里1、4、5、6都是正常的lock/unlock,2、3是需要特别说明的。2是进入pthread_cond_wait后的,pthread_cond_wait调的pthread_mutex_unlock,这样做的目的是为了保证在thread1阻塞wait后,thread2获取同一把锁mutex的时候,能够正常获取(即5,6)。3是thread1被唤醒后,要退出pthead_cond_wait之前,pthread_cond_wait调的pthread_mutex_lock,这样做的目的是为了把mutex的控制权还给调用pthread_cond_wait的线程(即thread1)。
自旋锁
自旋锁与互斥锁类似,但他不是通过休眠使进程阻塞,而是在获取锁之前一直忙等(自旋)阻塞状态,可以用于:锁持有时间短,线程不希望在重新调度上花费太多成本。
http://www.cnblogs.com/yuuyuu/p/5140875.html
http://blog.csdn.net/fengbingchun/article/details/48579725 理解
屏障 barrier
协调多个线程并行工作的同步机制,允许每个线程等待,直到所有合作线程达到同一个点,然后从该点继续运行,pthread_join()就是一种barrier。
补充:
文件和文件锁
https://liwei.life/2016/07/31/file_and_filelock/
信号量
信号量是在互斥锁的基础上升级,互斥锁只允许一个线程进入临界区,而信号量允许多个线程同时进入临界区 。简单说,信号量就相当于一些令牌环,令牌环个数表示允许的线程个数,当然信号量保证了操作的原子性,原子性表示一件事情要不全部执行完且过程不中断,要不就不执行。原子性表示变量不会再执行过程中由其他程序改变。