线程
1.1 使用线程的理由
在面向线程设计的系统中,进程不是基本运行单位,而是线程的容器。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程可执行不同的任务。
进程有独立的地址空间,一个进程崩溃后,不会对其他进程产生影响。而线程只是一个进程中的不同执行路径,线程没有单独的地址空间,它们共享大部分数据,一个线程死掉就相当于整个进程死掉。
所以:多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行又要共享某些变量的并发操作,一般使用线程(进程间通信也能实现,但相对较麻烦)。
1.2 Linux上线程相关的API
功能:创建一个新的线程(pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。)
原型
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
参数
thread:返回线程ID
attr:设置线程的属性,attr为NULL表示使用默认属性
start_routine:是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数
新创建的线程从start_rtn
函数的地址开始运行,该函数只有一个无类型指针参数arg
。如果需要向start_rtn
函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg
参数传入。
功能:线程终止
原型
void pthread_exit(void *value_ptr);
参数
value_ptr:value_ptr不要指向一个局部变量。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身
参数:rval_ptr
是一个无类型指针(注意不要指向一个局部变量),与传给启动例程的单个参数类似。进程中的其他线程可以通过调用pthread_join
函数访问到这个指针。
功能:等待线程结束
原型
int pthread_join(pthread_t thread, void **value_ptr);
参数
thread:要等待的线程ID
value_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码
调用这个函数的线程将会一直阻塞,直到指定的线程调用pthread_exit
pthread_exit()函数将终止调用线程,且其返回值可由另一线程通过调用 pthread_join()来获取
功能:获取线程ID
原型
pthread_t pthread_self(void);
返回值
调用线程的ID
1.3 代码案例
#include <stdio.h>
#include <pthread.h>
void *func1(void *arg)
{
static int ret = 10;
printf("t1: %ld thread is create\n", (unsigned long)pthread_self());
printf("t1: param is %d\n", *((int *)arg));
pthread_exit((void *)&ret);//退出线程
}
int main()
{
int ret;
pthread_t t1;
int *pret = NULL;
int param = 100;
ret = pthread_create(&t1, NULL, func1, (void *)¶m);//创建线程
if (ret == 0)
{
printf("main: create t1 success\n");
}
printf("main:%ld\n", (unsigned long)pthread_self());
pthread_join(t1, (void **)&pret);//等待线程
printf("main: ti quit: %d\n", *pret);
return 0;
}
配合函数原型看 搞清楚以上代码为什么要使用强转符
打印结果如下
通过以上代码就创建了新的线程t1,同时打印了main线程和t1线程对应的线程ID,并且通过pthread_join和pthread_exit的使用 获取到了t1线程的退出码10。
互斥锁
首先上一段代码
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int g_data = 0;
void *func1(void *arg)
{
printf("t1: %ld thread is create\n", (unsigned long)pthread_self());
printf("t1: param is %d\n", *((int *)arg));
while (1)
{
printf("t1: %d\n", g_data++);
sleep(1);
if (g_data == 3)
pthread_exit(NULL);
}
}
void *func2(void *arg)
{
printf("t2: %ld thread is create\n", (unsigned long)pthread_self());
printf("t2: param is %d\n", *((int *)arg));
while (1)
{
printf("t2: %d\n", g_data++);
sleep(1);
}
}
int main()
{
int ret;
pthread_t t1;
pthread_t t2;
int param = 100;
ret = pthread_create(&t1, NULL, func1, (void *)¶m);
if (ret == 0)
{
printf("main: create t1 success\n");
}
ret = pthread_create(&t2, NULL, func2, (void *)¶m);
if (ret == 0)
{
printf("main: create t2 success\n");
}
printf("main:%ld\n", (unsigned long)pthread_self());
while (1)
{
printf("main: %d\n", g_data++);
sleep(1);
}
pthread_join(t1, NULL);
pthread_join(t2, NULL);
return 0;
}
打印结果
我们设置了一个全局变量g_data,目的想在g_data加到3的时候,退出t1线程。而实际情况是3可能会被t2线程或main线程 “抢走” ,t1仍然不停的在打印,所以可能实现不了预想功能。
于是我们想让g_data加到3的这个过程发生在t1线程中,这样就能实现该功能了。于是引入互斥锁。
2.1 互斥锁概念
在线程实际运行过程中,我们经常需要多个线程保持同步。这时可以用互斥锁来完成任务。
作用:防止多线程对同一个数据进行操作 。
2.2 与互斥锁相关的API
功能:创建及销毁互斥锁
原型
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
参数
mutex 所需创建的锁
attr 创建锁属性,一般默认为NULL
返回值
成功后返回0,其他任何返回值都表示出现错误。
以上为互斥锁的动态创建
也可以用宏PTHREAD_MUTEX_INITIALIZER来静态的初始化锁
互斥锁是pthread_mutex_t的结构体,而这个宏是一个结构常量,如下可以完成静态的初始化锁:
pthread_mutex_t mutex =PTHREAD_MUTEX_INITIALIZER;
功能: 加锁及解锁
原型
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
参数
mutex 所需操作的锁
使用互斥锁改进以上代码
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int g_data = 0;
pthread_mutex_t mutex;
void *func1(void *arg)
{
printf("t1: %ld thread is create\n", (unsigned long)pthread_self());
printf("t1: param is %d\n", *((int *)arg));
pthread_mutex_lock(&mutex); //上锁
while (1)
{
printf("t1: %d\n", g_data++);
sleep(1);
if (g_data == 3)
{
pthread_mutex_unlock(&mutex); //解锁
printf("t1 quit========================\n");
pthread_exit(NULL); //退出线程
//exit(0); 会直接退出进程
}
}
}
void *func2(void *arg)
{
printf("t2: %ld thread is create\n", (unsigned long)pthread_self());
printf("t2: param is %d\n", *((int *)arg));
while (1)
{
printf("t2: %d\n", g_data);
pthread_mutex_lock(&mutex); //上锁
g_data++;
pthread_mutex_unlock(&mutex); //解锁
sleep(1);
}
}
int main()
{
int ret;
pthread_t t1;
pthread_t t2;
int param = 100;
pthread_mutex_init(&mutex, NULL); //创建一把锁
ret = pthread_create(&t1, NULL, func1, (void *)¶m);
if (ret == 0)
{
printf("main: create t1 success\n");
}
ret = pthread_create(&t2, NULL, func2, (void *)¶m);
if (ret == 0)
{
printf("main: create t2 success\n");
}
printf("main:%ld\n", (unsigned long)pthread_self());
while (1)
{
printf("main: %d\n", g_data);
sleep(1);
}
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_mutex_destroy(&mutex); //销毁锁
return 0;
}
打印结果
以上代码中若1线程先运行 则 1 2 3 都是在1线程中加的
若2线程先运行 则 1是在2线程中加的 2 3 是在1线程中加的所以1线程一定能拿到3 正常退出线程
当1线程首先上锁后,会一直运行到1线程解锁,其余线程才会运行
条件变量
3.1 条件变量的概念
条件变量是线程另一可用的同步机制。条件变量给多个线程提供了一个会合的场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。可以让线程休眠等待某个条件,条件满足唤醒线程去检测这个条件是否真的满足的机制。
3.2 与条件变量相关的API
功能: 创建及销毁条件变量
原型
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
参数
cond 操作的条件变量
attr 一般为NULL
功能: 等待
原型
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);
参数
cond 操作的条件变量
mutex 所需操作的锁
功能: 触发
原型
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
参数
cond 操作的条件变量
代码案例
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int g_data = 0;
pthread_mutex_t mutex;
pthread_cond_t cond;
void *func1(void *arg)
{
printf("t1: %ld thread is create\n", (unsigned long)pthread_self());
printf("t1: param is %d\n", *((int *)arg));
while (1)
{
pthread_cond_wait(&cond, &mutex); //等待 处于睡眠状态 除非2线程唤醒它
printf("t1 run========================\n");
printf("t1: %d\n", g_data);
g_data = 0;
sleep(1);
}
}
void *func2(void *arg)
{
printf("t2: %ld thread is create\n", (unsigned long)pthread_self());
printf("t2: param is %d\n", *((int *)arg));
while (1)
{
printf("t2: %d\n", g_data);
pthread_mutex_lock(&mutex); //上锁
g_data++;
if (g_data == 3)
{
pthread_cond_signal(&cond); //触发 g_data加到3时唤醒1线程内的等待
}
pthread_mutex_unlock(&mutex); //解锁
sleep(1);
}
}
int main()
{
int ret;
pthread_t t1;
pthread_t t2;
int param = 100;
pthread_mutex_init(&mutex, NULL); //创建一把锁
pthread_cond_init(&cond, NULL); //创建一个条件
ret = pthread_create(&t1, NULL, func1, (void *)¶m);
ret = pthread_create(&t2, NULL, func2, (void *)¶m);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_mutex_destroy(&mutex); //销毁锁
pthread_cond_destroy(&cond); //销毁条件
return 0;
}
打印结果
以上代码实现了在线程t1中每次将g_data加到3后运行线程t1,同时清零g_data。