目录
一.基本概念
二.线程
1. 线程创建
2. 线程退出
3. 线程等待
4. 线程创建Demo
三. 互斥锁
1. 创建及销毁互斥锁
2. 加锁及解锁
3. 互斥锁Demo
4.面试问题(进入死锁)
四. 条件
1. 创建及销毁条件变量
2. 等待
3. 触发
4. 条件Demo
五. 程序员检查代码小技巧
一.基本概念
典型的UNIX/Linux进程可以看成只有一个控制线程:一个进程在同一时刻只做一件事情。有了多个控制线程后,在程序设计时可以把进程设计成在同一时刻做不止一件事,每个线程各自处理独立的任务。
父进程创建子进程,都会消耗内存(拷贝)。进程是程序执行时的一个实例,是担当分配系统资源(CPU时间、内存等)的基本单位。进程本身不是基本运行单位,而是线程的容器。程序本身只是指令、数据及其组织形式的描述,进程才是程序(那些指令和数据)的真正运行实例。程序是静态的,进程是动态的。
"进程——资源分配的最小单位,线程——程序执行的最小单位"多进程程序要比多线程程序健壮。
多线程开发在 Linux 平台上已经有成熟的 pthread 库支持。其涉及的多线程开发的最基本概念主要包含三点:线程,互斥锁,条件。其中,线程操作又分线程的创建,退出,等待 3 种。互斥锁则包括 4 种操作,分别是创建,销毁,加锁和解锁。条件操作有 5 种操作:创建,销毁,触发,广播和等待。
二.线程
1. 线程创建
#include <pthread.h>
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
当pthread_create成功返回时,由tidp指向的内存单元被设置为新创建线程的线程ID。attr参数用于定制各种不同的线程属性,暂可以把它设置为NULL,以创建默认属性的线程。
新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg。如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。
所有代码都是 返回:若成功返回0,否则返回错误编号
2. 线程退出
int pthread_exit(void *rval_ptr);
单个线程可以通过以下三种方式退出,在不终止整个进程的情况下停止它的控制流:
1)线程只是从启动例程中返回,返回值是线程的退出码。
2)线程可以被同一进程中的其他线程取消。
3)线程调用pthread_exit:
ptr是一个指针。进程中的其他线程可以调用join访问。
3. 线程等待
int pthread_join(pthread_t thread, void **rval_ptr);
调用这个函数的线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回或者被取消。如果例程只是从它的启动例程返回i,如果对线程的返回值不感兴趣,可以把rval_ptr置为NULL。
4. 线程创建Demo
void *func1(void *arg)
{
static int ret=99; //表示固定数据
printf("t1 arg=%d\n",*(int *)arg); //arg是func1的传参先(int *)转换,再取内容
printf("t1 id=%d\n",(int)pthread_self());
pthread_exit((void*)&ret);
}
int main ()
{
pthread_t t1;
int *pret=NULL;
int arg=100;
int ret;
ret=pthread_create(&t1,NULL,func1,(void*)&arg); //都是指针,先取地址,在(void*)转换
if(ret==0){
printf("creat yes\n");
}
printf("main id=%d\n",(int)pthread_self()); // 打印自己的id号
// int pthread_join(pthread_t thread, void **rval_ptr);
pthread_join(t1,(void**)&pret); //先将pret取地址为二级指针,在转换为void型
printf("pret=%d\n",*pret);
return 0;
}
三. 互斥锁
互斥量(mutex)从本质上来说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。
定义全局变量
pthread_mutex_t mutex;
1. 创建及销毁互斥锁
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t mutex);
// 返回:若成功返回0,否则返回错误编号
要用默认的属性初始化互斥量,只需把attr设置为NULL。
2. 加锁及解锁
int pthread_mutex_lock(pthread_mutex_t mutex);
int pthread_mutex_trylock(pthread_mutex_t mutex);
int pthread_mutex_unlock(pthread_mutex_t mutex);
// 返回:若成功返回0,否则返回错误编号
3. 互斥锁Demo
int g_data=0; // 全局变量
pthread_mutex_t mutex;
void *func1(void *arg)
{
printf("t1 arg=%d\n",*(int *)arg);
printf("t1 id=%d\n",(int)pthread_self());
pthread_mutex_lock(&mutex); // 上锁
while(1){
printf("t1=%d\n",g_data++);
sleep(1);
if(g_data==3){
printf("t1= 3 let us quit\n");
pthread_exit(NULL);
}
pthread_mutex_unlock(&mutex); //解锁
}
}
void *func2(void *arg)
{
printf("t2 arg=%d\n",*(int *)arg);
printf("t2 id=%d\n",(int)pthread_self());
while(1){
pthread_mutex_lock(&mutex);
printf("t2=%d\n",g_data++);
pthread_mutex_unlock(&mutex);
sleep(1);
}
}
int main ()
{
int arg=100;
int ret;
pthread_t *t1;
pthread_t *t2;
t1=(pthread_t*)malloc(sizeof(pthread_t)*128);
t2=(pthread_t*)malloc(sizeof(pthread_t)*128); //这里如果用指针定义的话一定要开辟
空间否则会出现段错误
pthread_mutex_init(&mutex,NULL);
ret=pthread_create(t1,NULL,func1,(void*)&arg);
ret=pthread_create(t2,NULL,func2,(void*)&arg);
printf("main id=%d\n",(int)pthread_self());
pthread_join(*t1,NULL);
pthread_join(*t2,NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
其大致作用就是解释,被夹在pthread_mutex_lock(&mutex);和 pthread_mutex_unlock(&mutex);之间的代码不受干扰,先执行完,再去执行别的线程。这个demo的作用是想,当t1线程,g_data的值为3的时候直接调用pthread_exit(NULL);退出线程,或者是exit(0);直接退出进程.这个demo不够完善,下面介绍条件时候会有最终完善效果。
4.面试问题(进入死锁)
pthread_mutex_t mutex;
pthread_mutex_t mutex2;
int i;
void *func1(void *arg)
{
pthread_mutex_lock(&mutex);
sleep(1);
pthread_mutex_lock(&mutex2);
printf("t1 arg=%d\n",*(int *)arg);
printf("t1 id=%d\n",(int)pthread_self());
pthread_mutex_unlock(&mutex);
}
void *func2(void *arg)
{
pthread_mutex_lock(&mutex2);
sleep(1);
pthread_mutex_lock(&mutex);
printf("t2 arg=%d\n",*(int *)arg);
printf("t2 id=%d\n",(int)pthread_self());
pthread_mutex_unlock(&mutex);
}
出现在有两把锁的情况下,mutex和mutex2在两个函数。列子:锁2被线程2拿走了,线程2调用锁1的时候,线程1里面有一个锁2。一直无法被调用。
四. 条件
全局变量
pthread_cond_t cond;
1. 创建及销毁条件变量
#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 cond);
// 返回:若成功返回0,否则返回错误编号
除非需要创建一个非默认属性的条件变量,否则pthread_cont_init函数的attr参数可以设置为NULL。
2. 等待
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, cond struct timespec *restrict timeout);
pthread_cond_wait等待条件变为真。如果在给定的时间内条件不能满足,那么会生成一个代表一个出错码的返回变量。传递给pthread_cond_wait的互斥量对条件进行保护,调用者把锁住的互斥量传给函数。函数把调用线程放到等待条件的线程列表上,然后对互斥量解锁,这两个操作都是原子操作。这样就关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道,这样线程就不会错过条件的任何变化。pthread_cond_wait返回时,互斥量再次被锁住。
pthread_cond_timedwait函数的工作方式与pthread_cond_wait函数类似,只是多了一个timeout。timeout指定了等待的时间,它是通过timespec结构指定。
3. 触发
int pthread_cond_signal(pthread_cond_t cond);
int pthread_cond_broadcast(pthread_cond_t cond);
这两个函数可以用于通知线程条件已经满足。pthread_cond_signal函数将唤醒等待该条件的某个线程,而pthread_cond_broadcast函数将唤醒等待该条件的所有进程。
4. 条件Demo
int g_data=0;
pthread_mutex_t mutex;
pthread_cond_t cond;
void *func1(void *arg)
{
pthread_mutex_lock(&mutex);
static int cnt = 0; //用来检测计数
while(1){
pthread_cond_wait(&cond,&mutex); //等待锁和条件
sleep(1);
printf("cond t1=%d\n",g_data);
// pthread_exit(NULL);
g_data=0; //重置g_data为0
sleep(1);
if(cnt++ == 10){
exit(1);
}
pthread_mutex_unlock(&mutex); //解锁,只有完成了上述代码才能再去func2
}
}
void *func2(void *arg)
{
while(1){
pthread_mutex_lock(&mutex);
printf("t2=%d\n",g_data++);
pthread_mutex_unlock(&mutex);
if(g_data == 3){
pthread_cond_signal(&cond); //触发信号
}
sleep(1);
}
}
int main ()
{
int arg=100;
int ret;
pthread_t t1;
pthread_t t2;
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&cond,NULL); //创建条件
ret=pthread_create(&t1,NULL,func1,(void*)&arg);
ret=pthread_create(&t2,NULL,func2,(void*)&arg);
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mutex); //销毁条件
return 0;
}
运行结果:
当t2线程到3的时候触发t1线程,刷新全局变量data,再次跳转到t2线程
五. 程序员检查代码小技巧
static int cnt = 0;
if(cnt++ == 10){
exit(1);
}
#include <stdio.h>
int main(int argc,char **argv)
{
int time=atoi(argv[1]);
int i;
for(i=0;i<time;i++){
system("./cond");
}
}
10为参数,将这个代码运行100遍,放到test.txt 文件之中
撒花~