目录
线程就是轻量级进程,线程是最小的执行单位,进程是最小的资源分配单位。
线程的创建与退出
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);、
/*
函数参数:
pthread_t:传出参数,保存系统为我们分配好的线程ID
当前Linux中可理解为:typedef unsigned long int pthread_t。
attr:通常传NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。
start_routine:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。
arg:线程主函数执行期间所使用的参数。
返回值
成功,返回0
失败,返回错误号
*/
void pthread_exit(void *retval);
//函数参数
// retval表示线程退出状态,通常传NULL
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
const int n = 5;
void *mythread(void *arg)
{
int i = *(int *) arg;
printf("the %d-th sub-thread, p_id = %ld\n",i,pthread_self());
pthread_exit();
}
int main()
{
pthread_t thread[n];
int arr[n]];
int ret = 0;
for(int i = 0; i < n)
{
arr[i] = i;
ret = pthread_create(&thread[i],NULL,mythread,&arr[i]);
if(ret != 0)
{
printf("pthread_create error\n");
return -1;
}
}
printf("main thread pid = %d id == %ld\n",getpid(),pthread_self());
sleep(10);
return 0;
}
线程属性
linux下线程的属性是可以根据实际项目需要,进行设置,线程创建时采用线程的默认属性,可以根据需要设置线程的分离属性。
线程的分离状态决定一个线程以什么样的方式来终止自己,有两种状态:
非分离状态:线程的默认属性是非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。
分离状态:分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。应该根据自己的需要,选择适当的分离状态。
将线程设置为分离属性
//第1步:定义线程属性类型类型的变量
pthread_attr_t attr;
//第2步:对线程属性变量进行初始化
int pthread_attr_init (pthread_attr_t* attr);
//第3步:设置线程为分离属性
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
/*
参数:
attr: 线程属性
detachstate:
PTHREAD_CREATE_DETACHED(分离)
PTHREAD_CREATE_JOINABLE(非分离)
*/
pthread_create(&thread, &attr, mythread, NULL);
也可以用pthread_detach函数)来设置线程分离,pthread_detach函数是在创建线程之后调用的。
int pthread_detach(pthread_t thread);
//获取线程的退出状态 默认
int pthread_join(pthread_t thread, void **retval);
不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL错误。也就是说,如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。
线程同步
线程同步,指一个线程发出某一功能调用时,在没有得到结果之前,该调用不返回。同时其它线程为保证数据一致性,不能调用该功能。
互斥锁
pthread_mutex_t mutex; //变量mutex只有两种取值1、0。
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
/*
函数描述:
初始化一个互斥锁(互斥量) ---> 初值可看作1
函数参数
mutex:传出参数,调用时应传 &mutex
attr:互斥锁属性。是一个传入参数,通常传NULL,选用默认属性(线程间共享)。
restrict关键字:只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,、
只能通过本指针完成。不能通过除本指针以外的其他变量或
指针修改互斥量mutex的两种初始化方式:
静态初始化:如果互斥锁 mutex 是静态分配的(定义在全局,或加了static关键字修饰),
可以直接使用宏进行初始化。
pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER;
动态初始化:局部变量应采用动态初始化。pthread_mutex_init(&mutex, NULL)
*/
int pthread_mutex_destroy(pthread_mutex_t *mutex);//销毁一个互斥锁
int pthread_mutex_lock(pthread_mutex_t *mutex); //对互斥所加锁,可理解为将mutex--
int pthread_mutex_unlock(pthread_mutex_t *mutex); //对互斥所解锁,可理解为将mutex ++
lock尝试加锁,如果加锁不成功,线程阻塞,阻塞到持有该互斥量的其他线程解锁为止。
unlock主动解锁函数,同时将阻塞在该锁上的所有线程全部唤醒,至于哪个线程先被唤醒,取决于优先级、调度。默认:先阻塞、先唤醒。
创建两个线程,让两个线程共享一个全局变量int number, 然后让每个线程数5000次数,可能出现某个线程少加的情况。这是因为线程操作共享资源的先后顺序不确定,可以出现覆盖值得现象,利用互斥锁来保证共享资源得同步。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>
//定义一把锁
pthread_mutex_t mutex;
int num = 0;
void *fun_thread1(void *arg)
{
int n;
for(int i = 0; i < 5000; i++)
{
pthread_mutex_lock(&mutex);
n = num;
n++;
num = n;
pthread_mutex_unlock(&mutex);
}
}
void *fun_thread2(void *arg)
{
int n;
for(int i = 0; i < 5000; i++)
{
pthread_mutex_lock(&mutex);
n = num;
n++;
num = n;
pthread_mutex_unlock(&mutex);
}
}
int main()
{
int ret = 0;
pthread_mutex_init(&mutex,NULL);
pthread_t thread1;
pthread_t thread2;
ret = pthread_create(&thread1,NULL,fun_thread1,NULL);
if(ret != 0)
{
return -1;
}
ret = pthread_create(&thread2,NULL,fun_thread2,NULL);
if(ret != 0)
{
return -1;
}
pthread_join(thread1,NULL); //非分离状态
pthread_join(thread2,NULL);
printf("num = %d\n",num);
pthread_mutex_destroy(&mutex); //释放锁
return 0;
}
使用互斥锁之后,两个线程由并行变为了串行,效率降低了,但是可以使两个线程同步操作共享资源,从而解决了数据不一致的问题。
死锁
死锁使用互斥锁不当引起的一种现象。当一个线程自己锁自己,即调用两次互斥锁的上锁过程会出现死锁。此外,当线程A拥有A锁,请求获得B锁;线程B拥有B锁,请求获得A锁,这样造成线程A和线程B都不释放自己的锁,而且还想得到对方的锁,从而产生死锁。
解决死锁:
让线程按照一定的顺序去访问共享资源
在访问其他锁的时候,需要先将自己的锁解开
调用pthread_mutex_trylock,如果加锁不成功会立刻返回
读写锁
读写锁也叫共享-独占锁。当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的。写独占、读共享。适用于读次数远大于写的情况。
对写加锁时,解锁前,所有对该锁加锁的线程都会被阻塞。 对读加锁时,如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻塞。
pthread_rwlock_t rwlock; //定义
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); //初始化
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);//销毁读写锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); //加读锁
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);//尝试加读锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);//加写锁
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);//尝试加写锁
int pthread_rwlock_unlock(&pthread_rwlock_t *rwlock);//解锁
读并行,写独占,当读写同时等待锁的时候写的优先级高。
3个线程不定时写同一全局资源,5个线程不定时读同一全局资源。
#include <stdio.h>
#include <pthread.h>
//定义一把读写锁
pthread_rwlock_t rwlock;
const int n = 8;
int number = 0;
//线程回调函数--写
void *write_thread(void *arg)
{
int i = *(int *) arg;
int cur = 0;
while(1)
{
//写线程加写锁,其他线程不能写
pthread_rwlock_wrlock(&rwlock);
cur = number;
cur ++;
number = cur;
printf("the %d-th thread write number = %d\n",i,number);
//解锁
pthread_rwlock_unlock(&rwlock);
sleep(rand()%5);
}
}
void *read_thread(void *arg)
{
int i = *(int *) arg;
int cur = 0;
while(1)
{
//读线程加读锁,
pthread_rwlock_rdlock(&rwlock);
cur = number;
printf("the %d-th thread read number = %d\n",i,cur);
//解锁
pthread_rwlock_unlock(&rwlock);
sleep(rand()%5);
}
}
int main()
{
int ret = 0;
pthread_rwlock_init(&rwlock,NULL);
pthread_t thread[n];
int arr[n];
//创建3个写线程
for(int i = 0; i < 3; i++)
{
arr[i] = i;
ret = pthread_create(&thread[i],NULL,write_thread,&arr[i]);
if(ret != 0)
{
return -1;
}
}
//创建5个读线程
for(int i = 3; i < n; i++)
{
arr[i] = i;
ret = pthread_create(&thread[i],NULL,read_thread,&arr[i]);
if(ret != 0)
{
return -1;
}
}
for(int i = 0; i < n; i++)
{
pthread_join(thread[i],NULL);
}
pthread_rwlock_destroy(&rwlock);
return 0;
}
结果如下:
条件变量
条件本身不是锁!但它也可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个会合的场所。使用互斥量保护共享数据;使用条件变量可以使线程阻塞,等待某个条件的发生, 当条件满足的时候解除阻塞。
条件变量两个动作:条件满足---解除阻塞,不满足----阻塞。
pthread_cond_t cond; //定义
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr); //初始化
int pthread_cond_destroy(pthread_cond_t *cond); //销毁条件变量
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
/*
函数描述:
条件不满足, 引起线程阻塞并解锁;
条件满足, 解除线程阻塞, 并加锁。
函数参数:
cond: 条件变量
mutex: 互斥锁变量
*/
int pthread_cond_signal(pthread_cond_t *cond);//唤醒至少一个阻塞在该条件变量上的线程
生产者消费者模型
#include <stdio.h>
#include <pthread.h>
typedef struct node
{
int val;
struct node * next;
}Node;
Node *head = nullptr;
//定义一把锁
pthread_mutex_t mutex;
//定义条件变量
pthread_cond_t cond;
//生产者线程
void *producer(void *arg)
{
Node *pNode = NULL;
int n = *(int *)arg;
while(1)
{
//生产一个节点
pNode = (Node *)malloc(sizeof(Node));
if(pNode==NULL)
{
perror("malloc error");
exit(-1);
}
pNode->val = rand()%1000;
printf("the %d-th thread produce int %d\n", n, pNode->val);
//加锁
pthread_mutex_lock(&mutex);
pNode->next = head;
head = pNode;
//解锁
pthread_mutex_unlock(&mutex);
//通知消费者线程解除阻塞
pthread_cond_signal(&cond);
sleep(rand()%3);
}
}
//消费者线程
void *consumer(void *arg)
{
Node *pNode = NULL;
int n = *(int *)arg;
while(1)
{
//加锁
pthread_mutex_lock(&mutex);
if(head==NULL)
{
//若条件不满足,需要阻塞等待
//若条件不满足,则阻塞等待并解锁;
//若条件满足(被生成者线程调用pthread_cond_signal函数通知),解除阻塞并加锁
pthread_cond_wait(&cond, &mutex);
}
if(head==NULL)
{
//解锁
pthread_mutex_unlock(&mutex);
continue;
}
printf("the %d-th constumer take int %d\n", n, head->val);
pNode = head;
head = head->next;
//解锁
pthread_mutex_unlock(&mutex);
free(pNode);
pNode = NULL;
sleep(rand()%3);
}
}
int main()
{
int ret;
int i = 0;
pthread_t thread1[5];
pthread_t thread2[5];
//初始化互斥锁
pthread_mutex_init(&mutex, NULL);
//条件变量初始化
pthread_cond_init(&cond, NULL);
int arr[5];
for(i=0; i<5; i++)
{
arr[i]= i;
//创建生产者线程
ret = pthread_create(&thread1[i], NULL, producer, &arr[i]);
if(ret!=0)
{
printf("pthread_create error, [%s]\n", strerror(ret));
return -1;
}
//创建消费者线程
ret = pthread_create(&thread2[i], NULL, consumer, &arr[i]);
if(ret!=0)
{
printf("pthread_create error, [%s]\n", strerror(ret));
return -1;
}
}
//等待线程结束
for(i=0; i<5; i++)
{
pthread_join(thread1[i], NULL);
pthread_join(thread2[i], NULL);
}
//释放互斥锁
pthread_mutex_destroy(&mutex);
//释放条件变量
pthread_cond_destroy(&cond);
return 0;
}
信号量
信号量相当于多把锁, 加强版的互斥锁。
sem_t sem; //定义
int sem_init(sem_t *sem, int pshared, unsigned int value); //初始化
//pshared: 0表示线程同步, 1表示进程同步
//value: 最多有几个线程操作共享数据
int sem_wait(sem_t *sem);//调用该函数一次, 相当于sem--, 当sem为0的时候, 引起阻塞
int sem_post(sem_t *sem);//调用一次, 相当于sem++
int sem_trywait(sem_t *sem);//尝试加锁, 若失败直接返回, 不阻塞
int sem_destroy(sem_t *sem);//销毁信号量
信号量实现生产者消费者模型
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
typedef struct node
{
int data;
struct node *next;
}Node;
Node *head = NULL;
//定义信号量
sem_t sem_producer;
sem_t sem_consumer;
//生产者线程
void *producer(void *arg)
{
Node *pNode = NULL;
while(1)
{
//生产一个节点
pNode = (Node *)malloc(sizeof(Node));
if(pNode==NULL)
{
perror("malloc error");
exit(-1);
}
pNode->data = rand()%1000;
printf("P:[%d]\n", pNode->data);
//加锁
sem_wait(&sem_producer); //--
pNode->next = head;
head = pNode;
//解锁
sem_post(&sem_consumer); //相当于++
sleep(rand()%3);
}
}
//消费者线程
void *consumer(void *arg)
{
Node *pNode = NULL;
while(1)
{
//加锁
sem_wait(&sem_consumer); //相当于--
printf("C:[%d]\n", head->data);
pNode = head;
head = head->next;
//解锁
sem_post(&sem_producer); //相当于++
free(pNode);
pNode = NULL;
sleep(rand()%3);
}
}
int main()
{
int ret;
pthread_t thread1;
pthread_t thread2;
//初始化信号量
sem_init(&sem_producer, 0, 5);
sem_init(&sem_consumer, 0, 0);
//创建生产者线程
ret = pthread_create(&thread1, NULL, producer, NULL);
if(ret!=0)
{
printf("pthread_create error, [%s]\n", strerror(ret));
return -1;
}
//创建消费者线程
ret = pthread_create(&thread2, NULL, consumer, NULL);
if(ret!=0)
{
printf("pthread_create error, [%s]\n", strerror(ret));
return -1;
}
//等待线程结束
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
//释放信号量资源
sem_destroy(&sem_producer);
sem_destroy(&sem_consumer);
return 0;
}
多线程环境
可重入函数
如果一个函数能够被多个线程同时调用且不发生竞态条件(不必担心数据是否会出错),则我们称它是可重入函数。