线程
一、线程概述(与进程的区别及线程的优势)
借鉴文章: http://t.zoukankan.com/xiehongfeng100-p-4620852.html
进程与线程:
进程是程序执行时的一个实例,是资源分配的最小单位,是线程的容器;线程——程序执行的最小单位"
使用线程的理由:
进程有独立的地址空间,线程没有单独的地址空间(同一进程内的线程共享进程的地址空间)。
- 它是一种非常"节俭"的多任务操作方式。
- 线程间方便的通信机制。
二、Linux上线程开发API概要
多线程开发在 Linux 平台上已经有成熟的 pthread 库支持。其涉及的多线程开发的最基本概念主要包含三点:线程,互斥锁,条件。其中,线程操作又分线程的创建,退出,等待 3 种。互斥锁则包括 4 种操作,分别是创建,销毁,加锁和解锁。条件操作有 5 种操作:创建,销毁,触发,广播和等待。其他的一些线程扩展概念,如信号灯等,都可以通过上面的三个基本元素的基本操作封装出来。详细请见下表:
三、线程的创建等待及退出
1、线程创建用pthread_create函数:
#include <pthread.h>
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t
*restrict attr, void *(*start_rtn)(void *), void *restrict arg);
参数:
- tidp:无符号的长整型的指针,指向线程ID
- attr:用于定制各种不同的线程属性,一般设置为NULL,以创建默认属性的线程
- start_rtn:函数指针,要干活的函数
- arg:要传参的参数,要是传多个参数,就要写个结构体,把结构体的地址传过去(这是个无类型的参数,这点很重要,传参的时候要转换成无类型的)
返回值:
- 若成功返回0
- 失败返回错误编号
2、线程的退出要用pthread_exit函数:
单个线程可以通过以下三种方式退出,在不终止整个进程的情况下停止它的控制流:
1)线程只是从启动例程中返回,返回值是线程的退出码。
2)线程可以被同一进程中的其他线程取消。
3)线程调用pthread_exit:
#include <pthread.h>
int pthread_exit(void *rval_ptr);
- rval_ptr是一个无类型指针,进程中的其他线程可以通过调用pthread_join函数访问到这个指针。(无类型很重要如:(void*)&ret)
- 返回状态值要自己定义一个数定义成static int 类型的,如果没有static函数调用结束后这块空间就释放了。
3、线程的等待用pthread_join函数:
#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr);
参数:
- thread:要等待的线程
- rval_ptr:这是个无类型二级指针,可以用来收集状态码的,用的时候要强转(如:(void**)&pret)
- 调用这个函数的线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回或者被取消。
- 如果对线程的返回值不感兴趣,可以把rval_ptr置为NULL。在这种情况下,调用pthread_join函数将等待指定的线程终止,但并不获得线程的终止状态。
返回值:
- 若成功返回0
- 失败返回错误编号
例子:
#include<stdio.h>
#include<pthread.h>
void *func1(void *arg)
{
static char *p = "t1 is run out";
printf("t1:%ld thread is create\n",(unsigned
long)pthread_self());
printf("t1:arg = %d\n",*((int *)arg));
pthread_exit((void *)p);
}
int main()
{
// int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
int ret;
pthread_t t1;
int arg = 100;
char *pret;
ret = pthread_create(&t1,NULL,func1,(void *)&arg);
if(ret == 0){
printf("main:creat t1 success\n");
}
printf("main:%ld\n",(unsigned long)pthread_self());
// while(1);
pthread_join(t1,(void **)&pret);
printf("main: t1 quit: %s\n",pret);
return 0;
}
四、线程同步之互斥量加锁解锁
互斥量:
互斥量从本质上来说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一个变为可运行状态的线程可以对互斥量加锁,其他线程将会看到互斥锁依然被锁住,只能回去等待它重新变为可用。在这种方式下,每次只有一个线程可以向前运行。
理解:说白了互斥量就是被锁住的部分,把他锁住保证了只有这块代码运行完了才能运行其他代码,并不是保证了这个线程先运行
1. 创建及销毁互斥锁:
锁是一个phread_mutex_t 类型的变量,要定义成全局变量
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(phread_mutex_t *mutex);
// 返回:若成功返回0,否则返回错误编号
参数:
- restrict mutex:这是一个指针,指向锁的地址
- restrict attr:要用默认的属性初始化互斥量,只需把attr设置为NULL
- mutex:这是一个指针,指向锁的地址
2. 加锁及解锁:
#include <pthread.h>
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,否则返回错误编号
如果线程不希望被阻塞,它可以使用pthread_mutex_trylock尝试对互斥量进行加锁。如果调用pthread_mutex_trylock时互斥量处于未锁住状态,那么pthread_mutex_trylock将锁住互斥量,不会出现阻塞并返回0,否则pthread_mutex_trylock就会失败,不能锁住互斥量,而返回EBUSY。
例子:
#include<stdio.h>
#include<pthread.h>
pthread_mutex_t mutex;
void *func1(void *arg)
{
int i;
pthread_mutex_lock(&mutex);
for(i=0;i<5;i++){
printf("t1:%ld thread is create\n",
(unsigned long)pthread_self());
printf("t1:arg = %d\n",*((int *)arg));
sleep(1);
}
pthread_mutex_unlock(&mutex);
}
void *func2(void *arg)
{
pthread_mutex_lock(&mutex);
printf("t2:%ld thread is create\n",
(unsigned long)pthread_self());
printf("t2:arg = %d\n",*((int *)arg));
pthread_mutex_unlock(&mutex);
}
void *func3(void *arg)
{
pthread_mutex_lock(&mutex);
printf("t3:%ld thread is create\n",
(unsigned long)pthread_self());
printf("t3:arg = %d\n",*((int *)arg));
pthread_mutex_unlock(&mutex);
}
int main()
{
// int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
int ret;
pthread_t t1;
int arg = 100;
pthread_t t2;
pthread_t t3;
pthread_mutex_init(&mutex,NULL);
ret = pthread_create(&t1,NULL,func1,(void *)&arg);
if(ret == 0){
printf("main:creat t1 success\n");
}
ret = pthread_create(&t2,NULL,func2,(void *)&arg);
if(ret == 0){
printf("main:creat t2 success\n");
}
ret = pthread_create(&t3,NULL,func3,(void *)&arg);
if(ret == 0){
printf("main:create t3 success\n");
}
printf("main:%ld\n",(unsigned long)pthread_self());
// while(1);
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_mutex_destroy(&mutex);
return 0;
五、互斥锁限制共享资源的访问
这个就是旧知识新用法。
例子:
#include<stdio.h>
#include<pthread.h>
#include <stdlib.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:arg = %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:arg = %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 pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
int ret;
pthread_t t1;
int arg = 100;
pthread_t t2;
pthread_mutex_init(&mutex,NULL);
ret = pthread_create(&t1,NULL,func1,(void *)&arg);
if(ret == 0){
printf("main:creat t1 success\n");
}
ret = pthread_create(&t2,NULL,func2,(void *)&arg);
if(ret == 0){
printf("main:creat t2 success\n");
}
while(1){
printf("main:%d\n",g_data);
sleep(1);
}
printf("main:%ld\n",(unsigned long)pthread_self());
// while(1);
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
六、什么情况造成死锁
什么是死锁:
由于互斥锁的使用不当,导致多个线程无法进行下一步的代码运行,也就是说这两个竞争锁的线程卡住了,以至于其他的线程无法获得锁,导致程序停止不前。
什么情况造成死锁:
前提是有两把锁,当线程A获得一把锁的时候想获得另一把锁,而线程B手里握着线程A想获得的那把锁,同时,它也想获得线程A握着的锁,导致线程A线程B都想拿到对方手里的锁。
七、线程条件控制实现线程的同步
条件是一个pthread_cond_t 类型的变量,要定义成全局变量
条件使用的步骤:
- 条件创建
- 线程条件等待
- 条件触发:单独去解决一条等待的线程
- 或条件广播:向多条线程发消息
- 条件销毁
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,否则返回错误编号
参数:
- restrict cond:这是一个指针,指向条件的地址
- restrict attr:要用默认的属性初始化条件,只需把attr设置为NULL
- cond:这是一个指针,指向条件的地址
2、线程条件等待:
条件是加到锁上的一个条件
#include <pthread.h>
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);//这个函数就是加了个时间,
一定函数内线程会等待。
// 返回:若成功返回0,否则返回错误编号
参数:
- restrict cond:这是一个指针,指向条件的地址
- restrict mutex:这是一个指针,指向锁的地址
3、条件触发和广播:
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);//指针,只向条件
int pthread_cond_broadcast(pthread_cond_t *cond);
// 返回:若成功返回0,否则返回错误编号
这两个函数可以用于通知线程条件已经满足。pthread_cond_signal函数将唤醒等待该条件的某个线程,而pthread_cond_broadcast函数将唤醒等待该条件的所有进程。
注意一定要在改变条件状态以后再给线程发信号。
4、线程静态初始化:
所谓的静态初始化就是在定义变量是,让其初始化。
pthread_cond_t cond;
//动态初始化: pthread_cond_init(&cond, NULL);
//静态初始化: pthread_cond_t cond= PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex;
//动态初始化: pthread_mutex_init(&mutex,NULL);
//静态初始化: pthread_mutex_t mutex= PTHREAD_MUTEX_INITIALIZER;
可以学习下Linux线程生产者与消费者,这是对缓冲区数据的访问:https://blog.csdn.net/qq_35116371/article/details/73252627