Linux下实现多线程都是用pthread。UNIX以及类UNIX系统中,线程是以
轻量级进程的形式实现。在linux内核中,
每个线程也拥有独立的task_struct结构,因此,每个线程也拥有自己独立的pid。
一个进程中可以包含多个同时运行的线程,这些线程共享了同一个虚拟内存地址空间和系统资源。
1 创建进程时,直接使用系统调用:clone(),fork()也是调用clone()。
2 创建POSIX线程:pthread_create(),实际也是调用的clone()。
一. 最简单的多线程
创建5个线程:
#include <iostream>
#include <pthread.h> //多线程相关操作头文件
using namespace std;
#define NUM_THREADS 5 //线程数
//函数返回的是函数指针,便于后面作为参数
void* say_hello(void* args)
{
cout << "hello..." << endl;
return 0;
}
int main()
{
pthread_t tids[NUM_THREADS]; //线程id
for( int i = 0; i < NUM_THREADS; ++i )
{
//参数:创建的线程id,线程参数,线程运行函数的起始地址,运行函数的参数
int ret = pthread_create(&tids[i], NULL, say_hello, NULL);
if( ret != 0 )
{
cout << "pthread_create error:error_code=" << ret << endl;
}
}
//等待各个线程退出后,进程才结束,否则进程强制结束,线程处于未终止的状态
pthread_exit(NULL);
}
运行结果:
hello...
hello...hello...
hello...
hello...
打印出乱序的原因是因为多线程在竞争CPU资源,争夺运行权。
二. 类中的线程
线程调用到函数在一个类中,那必须将该函数声明为静态函数函数。因为静态成员函数属于静态全局区,线程可以共享这个区域,故可以各自调用。
类:
#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;
class Thread{
private:
static void* thread(void*);//静态函数
public:
int simpleThread();
};
int Thread::simpleThread()
{
pthread_t id;
int ret = pthread_create(&id, NULL,thread, NULL);
if(ret) {
cerr << "Create pthread error!" << endl;
return 1;
}
pthread_join(id, NULL);
return 0;
}
void* Thread::thread(void* ptr)
{
for(int i = 0;i < 3;i++)
{
sleep(1);
cout << "This is a pthread." << endl;
}
return 0;
}
主函数:
#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include "Thread.h"
using namespace std;
int main()
{
Thread* newTh = new Thread();
newTh->simpleThread();
return 0;
}
运行结果:
This is a pthread.
This is a pthread.
This is a pthread.
三. 带参数的线程调用
#include <iostream>
#include <pthread.h>
using namespace std;
#define NUM_THREADS 5 //线程数
void* say_hello(void* args)
{
int i = *((int*)args); //对传入的参数进行强制类型转换,由无类型指针转变为整形指针,再用*读取其指向到内容
cout << "hello " <<i<< endl;
return 0;
}
int main()
{
pthread_t tids[NUM_THREADS]; //线程id
for( int i = 0; i < NUM_THREADS; ++i )
{
//参数:创建的线程id,线程参数,线程运行函数的起始地址,运行函数的参数
//传入到函数的参数必须强转为void*类型,即无类型指针,&i表示取i的地址,即指向i的指针
int ret = pthread_create(&tids[i], NULL, say_hello, (void*)&i);
pthread_join(tids[i], NULL); //pthread_join用来等待一个线程的结束,是一个线程阻塞的函数
if( ret != 0 )
{
cout << "pthread_create error:error_code=" << ret << endl;
}
}
//等待各个线程退出后,进程才结束,否则进程强制结束,线程处于未终止的状态
pthread_exit(NULL);
}
运行结果:
hello 0
hello 1
hello 2
hello 3
hello 4
这次运行的结果是按顺序的,因为每创建一个线程后都调用了pthread_join函数。
thread 1:after sum = 1
thread 2:before sum =1
thread 2:after sum = 3
finally,sum=3
四.线程同步--互斥锁
互斥锁是实现线程同步的一种机制,只要在临界区前后对资源加锁就能阻塞其他进程的访问。
加锁后,锁内的代码只能由该线程独占,其他线程无法调用。
加锁后,锁内的代码只能由该线程独占,其他线程无法调用。
#include <iostream>
#include <pthread.h>
using namespace std;
#define NUM_THREADS 2 //线程数
int sum = 0; //定义全局变量,让所有线程同时写,这样就需要锁机制
pthread_mutex_t sum_mutex; //互斥锁
void* say_hello(void* args)
{
int i = *((int*)args);
pthread_mutex_lock(&sum_mutex ); //先加锁,再修改sum的值,锁被占用就阻塞,直到拿到锁再修改sum;
cout << "thread "<<i<<":before sum =" << sum <<endl;
sum += i;
cout << "thread "<<i<<":after sum = " << sum <<endl;
pthread_mutex_unlock( &sum_mutex ); //释放锁,供其他线程使用
return 0;
}
int main()
{
pthread_t tids[NUM_THREADS]; //线程id
pthread_mutex_init(&sum_mutex,NULL);
for( int i = 0; i < NUM_THREADS; i++ )
{
int ret = pthread_create(&tids[i], NULL, say_hello, (void*)&i);
}
for( int i = 0; i < NUM_THREADS; i++ )
{
pthread_join(tids[i], NULL);
}
cout<<"finally,sum="<<sum<<endl;
//等待各个线程退出后,进程才结束,否则进程强制结束,线程处于未终止的状态
pthread_exit(NULL);
}
运行结果一:
thread 1:before sum =0
thread 1:after sum = 1
thread 2:before sum =1
thread 2:after sum = 3
finally,sum=3
运行结果二:
thread 2:before sum =0
thread 2:after sum = 2
thread 2:before sum =2
thread 2:after sum = 4
finally,sum=4
thread 1,i=1,signal other threads
thread 1,i=2
thread 1,i=3,signal other threads
thread 1,i=4
thread 1,i=5,signal other threads
pthread_cond_wait
thread 2,i=6
thread 1,i=6
thread 1,i=7,signal other threads
thread 1,i=8
thread 1,i=9,signal other threads
pthread_cond_wait
thread 2,i=10
多文件编译,Makefile文件如下:
thread 2:after sum = 2
thread 2:before sum =2
thread 2:after sum = 4
finally,sum=4
为什么sum值还是不确定呢?i怎么会是1和2???
因为传进参数时,传递的是i=0的地址,i值在不断改变。用一个数组来存i值:
thread 0:before sum =0
thread 0:after sum = 0
thread 1:before sum =0
thread 1:after sum = 1
thread 2:before sum =1
thread 2:after sum = 3
finally,sum=3
这样就正确了,按顺序打印出来了,互斥锁的作用起到了。
因为传进参数时,传递的是i=0的地址,i值在不断改变。用一个数组来存i值:
int index[NUM_THREAD];//用来保存i的值避免被修改
pthread_create(&tids[i], NULL, say_hello, (void*)&(index[i]));
运行结果(改成3个线程):
thread 0:before sum =0
thread 0:after sum = 0
thread 1:before sum =0
thread 1:after sum = 1
thread 2:before sum =1
thread 2:after sum = 3
finally,sum=3
这样就正确了,按顺序打印出来了,互斥锁的作用起到了。
五. 线程同步--条件变量
条件变量是线程同步的另一种实现机制,操作有signal和wait。条件变量总是和互斥量结合使用,条件变量就共享变量的状态改变发出通知,这里的信号,与linux中signal无关,意思为发出信号。
signal(发送信号):通知一个或多个处于等待状态的线程,某个共享变量的状态已经改变。
wait(等待):收到一个通知前一直处于阻塞状态。
与cond相关的判断代码设计时,必须是一个while循环来控制对wait函数的调用。
thread 1,i=0
#include <iostream>
#include <pthread.h>
using namespace std;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;/*初始化互斥锁*/
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;/*初始化条件变量*/
/*thread1打印10以内的偶数,thread2打印奇数*/
int i= 0;
void *thread1(void *args)
{
for(i=0;i<=9;i++)//i是全局变量
{
pthread_mutex_lock(&mutex);
if(i%2==0){
cout << "thread 1,i=" << i<<endl;
}else{
pthread_cond_signal(&cond);/*i变成奇数,条件改变,发送信号,通知其他线程*/
cout<<"thread 1,i="<<i<<",signal other threads"<<endl;
}
pthread_mutex_unlock(&mutex);
}
return 0;
}
void *thread2(void *args)
{
while(1)
{
pthread_mutex_lock(&mutex);
if(i%2==0){//i是偶数线程就挂起
/*操作有2步:
第一解锁,先解除之前的pthread_mutex_lock锁定的mutex;
第二挂起,阻塞并在等待队列里休眠,即所在线程挂起,直到再次被再次唤醒*/
pthread_cond_wait(&cond,&mutex);/*wait必须和互斥锁同时用在一个线程里,它同时起到对资源的加锁和解锁*/
cout<<"pthread_cond_wait"<<endl;
}
cout << "thread 2,i=" << i<<endl;
pthread_mutex_unlock(&mutex);
}
return 0;
}
int main()
{
pthread_t t_a;
pthread_t t_b;
pthread_create(&t_a,NULL,thread1,NULL);/*再创建进程t_a*/
pthread_create(&t_b,NULL,thread2,NULL); /*先创建进程t_b*/
pthread_join(t_a, NULL);/*等待进程t_a结束*/
pthread_join(t_b, NULL);/*等待进程t_b结束*/
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
运行结果(有多种运行结果):
thread 1,i=1,signal other threads
thread 1,i=2
thread 1,i=3,signal other threads
thread 1,i=4
thread 1,i=5,signal other threads
pthread_cond_wait
thread 2,i=6
thread 1,i=6
thread 1,i=7,signal other threads
thread 1,i=8
thread 1,i=9,signal other threads
pthread_cond_wait
thread 2,i=10
多文件编译,Makefile文件如下:
INC = -I./include
SRC = ./src
FLAGE = -pthread
CFLAG = -g -O3 -Wall -Wno-deprecated #-DSHOW_DEBUG #-pipe -D_NEW_LIC -D_GNU_SOURCE -D_REENTRANT -z defs
CC = g++
EXE_DIR = ./bin
EXE = $(EXE_DIR)/main.o \
$(EXE_DIR)/hello.o \
$(EXE_DIR)/para.o \
$(EXE_DIR)/mutex.o \
$(EXE_DIR)/signal.o \
all: $(EXE)
$(EXE_DIR)/%.o:$(SRC)/%.cpp
$(CC) $(CFLAG) $(FLAGE) $(INC) $< -o $@
clean:
rm -rf $(EXE_DIR)/*.o
rm -rf $(EXE)
总结:线程提供的同步机制是有代价,需要操作系统来调度,切换,加锁等,都是消耗资源的。
互斥量提供了对共享变量的独占式访问,条件变量允许一个或多个线程等待通知,其他线程改变了共享变量的状态。
线程的同步机制复杂,易出错,且开销比较大,所以衍生出了协程(微线程)的概念。