线程是程序中一个单一的顺序控制流程。进程内有一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指令运行时的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程
对线程的操作:
因为线程不属于系统调用,因此需要用到pthread.h库
1.创建线程
#include <pthread.h>
int pthread_create(pthread_t *thread,pthread_attr_t *attr,void *(*start_routine)(void *),void *arg);
//thread:线程id,返回型参数
//attr:线程属性,一般为NULL
//start_routine:所要执行的的函数(返回值void*,参数为void*)
//arg:函数的参数
2.终止线程
有三种方法可以终止一个线程:
a.通过return返回,对主线程不适用.
b.一个线程可以调用pthread_cancel终止其它线程.
c.调用pthread_exit终止自己.
void pthread_exit (void*retval);
//retval:返回值,可以返回给pthread_join函数
3.线程等待
根据线程的终止情况不同,pthread_join有以下三种情况:
a.如果线程通过return返回,则retval中存放线程的返回值
b.如果线程被异常终止,则retval中存放的是PTHREAD_CANCELED.
c.如果线程通过pthread_exit终止,则retval中存放的是pthread_exit的参数.
int pthread_join(pthread_t pthread,void **retval);
//pthread:需要等待的线程id
//retval:带回的参数
//成功返回0,失败返回错误号
4.线程的分离与结合
对于任意时间点,线程都是可分离的或者可结合的.
可结合的线程可以被其他线程收回资源或者杀死,在其他线程回收资源之前,它的资源是不会被释放的.
可分离的线程不能被其他线程回收或杀死,其资源是自动释放的.
默认情况下,线程都是可结合的.
如果一个可结合的线程运行完毕,没有被其他线程回收资源,那么它就会导致类似于僵尸进程的情况.
当有线程调用pthread_join回收其它线程时,如果其他线程没有运行结束,则调用者会阻塞,为了避免这种情况,我们可以将该线程分离出去.
分离线程需要调用:
int pthread_detach(thread_id); //一般是分离自己pthread_detach(pthread_self);
这样的话,就不必主线程去回收其资源,该线程运行完毕,会自动释放所有的资源.
eg:printtid.c:
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
void* thfu(void* arg) //线程函数
{
pid_t pid;
pthread_t tid;
pid=getpid();
tid=pthread_self();
printf("the thfu run:\npid:%d,tid:%d\n",pid,tid);
return NULL;
}
int main()
{
pid_t pid;
int err;
pthread_t tid;
pid=getpid(); //jin cheng id
tid=pthread_self(); //thread id
err=pthread_create(&tid,NULL,thfu,NULL); //create thread
if(err!=0)
{
perror("pthread_create error");
exit(0);
}
sleep(1); //parent sleep 1 second to make child run first
printf("the main run:\npid:%d,tid:%d\n",pid,tid);
exit(0);
}
运行结果:
线程同步与互斥:
- 互斥锁
通过锁的机制实现线程间的互斥,同一时刻只有一个线程可以锁定它,当一个锁被某个线程锁定的时候,如果有另外一个线程尝试锁定这个临界区(互斥体),则第二个线程会被阻塞,或者说被置于等待状态。只有当第一个线程释放了对临界区的锁定,第二个线程才能从阻塞状态恢复运行。
int pthread_mutex_init(pthread_mutex_t* mutex, const thread_mutexattr_t* mutexattr);初始化一个互斥锁。
int pthread_mutex_lock(pthread_mutex_t* mutex);如果mutex被锁定,当前进程处于等待状态;否则,本进程获得互斥锁并进入临界区。
int pthread_mutex_trylock(pthread_mutex_t* mutex);和lock不同的时候,尝试获得互斥锁不成功不会使得进程进入阻塞状态,而是继续返回线程执行。该函数可以有效避免循环等待锁,如果trylock失败可以释放已经占有的资源,这样可以避免死锁。
int pthread_mutex_unlock(pthread_mutex_t* mutex);释放互斥锁,并使得被阻塞的线程获得互斥锁并执行。
int pthread_mutex_destroy(pthread_mutex_t* mutex);用来撤销互斥锁的资源。
pthread_mutex_t mutex; pthread_mutex_init(&mutex,NULL); void pthread1(void* arg){ pthread_mutex_lock(&mutex); .....//临界区 pthread_mutex_unlock(&mutex); } void pthread2(void* arg){ pthread_mutex_lock(&mutex); .....//临界区 pthread_mutex_unlock(&mutex); }
eg:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//定义互斥变量,并初始化为快速互斥
int gn;
void* thread(void *arg)
{
printf("thread's ID is %d\n",pthread_self());
pthread_mutex_lock(&mutex);
gn = 12;
printf("Now gn = %d\n",gn);
pthread_mutex_unlock(&mutex);
return NULL;
}
int main()
{
pthread_t id;
printf("main thread's ID is %d\n",pthread_self());
gn = 3;
printf("In main func, gn = %d\n",gn);
if (!pthread_create(&id, NULL, thread, NULL)) {
printf("Create thread success!\n");
} else {
printf("Create thread failed!\n");
}
pthread_join(id, NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
运行结果:
- 读写锁
读写锁与互斥量类似,不过读写锁允许更高的并行性。适用于读的次数大于写的次数的数据结构。
一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。
读锁锁住,加读锁,可以;加写锁会被阻塞,但此时会阻塞后续的读锁请求,防止读锁长期占用无法进入写模式。写锁就是互斥锁。
int pthread_rwlock_init(pthread_rwlock_t* rwlock, const pthread_rwlockattr_t* attr);初始化读写锁
int pthread_destroy(pthread_rwlock_t* rwlock);销毁读写锁
int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock);加读锁
int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock);加写锁
int pthread_rwlock_unlock(pthread_rwlock_t* rwlock);解锁
eg:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
int gn;
pthread_rwlock_t q_rwlock;
void* thread(void *arg)
{
printf("thread's ID is %d\n",pthread_self());
pthread_rwlock_tryrdlock(&q_rwlock);
gn = 12;
printf("Now gn = %d\n",gn);
pthread_rwlock_unlock(&q_rwlock);
return NULL;
}
int main()
{
pthread_t id;
printf("main thread's ID is %d\n",pthread_self());
gn = 3;
printf("In main func, gn = %d\n",gn);
if (!pthread_create(&id, NULL, thread, NULL)) {
printf("Create thread success!\n");
} else {
printf("Create thread failed!\n");
}
pthread_join(id, NULL);
pthread_rwlock_destroy(&q_rwlock);
return 0;
}
运行结果同上
- 条件变量
信号量只有锁住和不锁两种状态,而且当条件变量和信号量一起使用时,允许线程以无竞争的方式等待特定的条件发生。
条件本身是由互斥量保护的:线程在改变条件状态之前必须先锁住互斥量。
int pthread_cond_init(pthread_cond_t* cond,const pthread_condattr_t* attr);初始化动态分配的条件变量;也可以直接用PTHREAD_INITIALIZER直接赋值给静态的条件变量
int pthread_cond_destroy(pthread_cond_t* cond)撤销条件变量资源;
int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);使用该函数使得等待条件变量为真,线程被条件变量cond阻塞。
int pthread_cond_timedwait(pthread_cond_t* cond, pthread_mutex_t* mutex,const struct timespec* tspr);与wait类似,只是经历tspr时间后,即使条件变量不满足,阻塞也被解除,返回错误码。确定到达什么时间要判定为超时
int pthread_cond_signal(pthread_cond_t* cond);唤醒因为条件变量阻塞的线程。
int pthread_cond_broadcast(pthread_cond_t* cond);唤醒等待该条件的所有线程。
pthread_cond_t cond;
pthread_mutex_t mutex;
int count=0;
void pthread1(void* arg){
pthread_mutex_lock(&mutex);
while(count==0)
pthread_cond_wait(&cond,&mutex);
count--;
pthread_mutex_unlock(&mutex);
}
void pthread2(void* arg){
pthread_mutex_lock(&mutex);
if(count==0)
pthread_cond_signal(&cond);
count++;
pthread_mutex_unlock(&mutex);
}
eg:生产者消费者:
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<pthread.h>
pthread_mutex_t cond_mutex=PTHREAD_MUTEX_INITIALIZER;//互斥变量
pthread_cond_t condition=PTHREAD_COND_INITIALIZER;//条件变量
int workcount=0;
#define MAX_CONSUMERS 10
void* producerThread(void* arg)
{
int i,j,ret;
double result=0.0;
printf("producer run\n");
for(i=0;i<30;i++)
{
ret=pthread_mutex_lock(&cond_mutex);
if(ret==0)
{
printf("produer creating work (%d)\n",workcount);
workcount++;
pthread_cond_broadcast(&condition);
pthread_mutex_unlock(&cond_mutex);
}
for(j=0;j<3000;j++)
{
result+=(double)random();
}
}
printf("producer finished\n");
pthread_exit(NULL);
}
void* consumerThread(void* arg)
{
int ret;
pthread_detach(pthread_self());
printf("consumer %x run\n",pthread_self());
while(1)
{
assert((pthread_mutex_lock(&cond_mutex))==0);
ret=pthread_cond_wait(&condition,&cond_mutex);
assert(ret==0);
if(workcount)
{
workcount--;
printf("consumer %x:do work(%d)\n",pthread_self(),workcount);
}
assert((pthread_mutex_unlock(&cond_mutex))==0);
}
printf("consumer %x:finished\n",pthread_self());
pthread_exit(NULL);
}
int main()
{
int i;
pthread_t consumers[MAX_CONSUMERS]={0};
pthread_t producer;
for(i=0;i<MAX_CONSUMERS;i++)
{//创建消费者线程
pthread_create(&consumers[i],NULL,consumerThread,NULL);
}
//创建消费者线程
pthread_create(&producer,NULL,producerThread,NULL);
pthread_join(producer,NULL);//挂起生产者线程
while(workcount>0);//等待工作完成
for(i=0;i<MAX_CONSUMERS;i++)
{
pthread_cancel(consumers[i]);
}
pthread_mutex_destroy(&cond_mutex);
pthread_cond_destroy(&condition);
return 0;
}
运行结果:
- 自旋锁
互斥量阻塞线程的方式是使其进入睡眠,而自旋锁是让线程忙等,即不会使其睡眠,而是不断循判断自旋锁已经被解锁。
适用于占用自旋锁时间比较短的情况。
- 信号量
介绍一下POSIX(POSIX标准定义了操作系统应该为应用程序提供的接口标准,换句话说,为一个POSIX兼容的操作系统编写的程序,应该可以在任何其它的POSIX操作系统(即使是来自另一个厂商)上编译执行。)的信号量机制,定义在头文件/usr/include/semaphore.h
1)初始化一个信号量:sem_init()
int sem_init(sem_t* sem,int pshared,unsigned int value);
pshared为0时表示该信号量只能在当前进程的线程间共享,否则可以进程间共享,value给出了信号量的初始值。
2)阻塞线程
sem_wait(sem_t* sem)直到信号量sem的值大于0,解除阻塞后将sem的值减一,表明公共资源经使用后减少;sem_trywait(sem_t* sem)是wait的非阻塞版本,它直接将sem的值减一,相当于P操作。
3)增加信号量的值,唤醒线程
sem_post(sem_t* sem)会使已经被阻塞的线程其中的一个线程不再阻塞,选择机制同样是由线程的调度策略决定的。相当于V操作。
3)释放信号量资源
sem_destroy(sem_t* sem)用来释放信号量sem所占有的资源
pthread_mutex_t mutex; sem_t full,empty; void producer(void* arg){ while(1){ sem_wait(&empty);//need to produce. the the empty of resource need minus 1 pthread_mutex_lock(&mutex); ...//produce a resource pthread_mutex_unlock(&mutex); sem_post(&full); //have produced a resource, the the full of resource need add 1 } } void consumer(void* arg){ while(1){ sem_wait(&full); pthread_mutex_lock(&mutex); ...//consume a resource pthread_mutex_unlock(&mutex); sem_post(&empty); } }