一、简介
1、线程与进程间对比 二、线程的相关属性及其使用方法
2、pthread_create3、pthread_exit4、pthread_join5、pthread_detach6、pthread_cancel 三、线程同步
1、简介2、互斥量
2.1 互斥量如何确保共享资源不被其他线程干扰呢2.2 pthread_mutex_init2.3 pthread_mutex_lock2.4 pthread_mutex_unlock2.5 pthread_mutex_destroy2.5 如何避免死锁 3、条件变量
3.1 pthread_cond_init3.2 pthread_cond_destroy3.3 pthread_cond_wait3.4 pthread_cond_signal 四、线程安全
1、函数的可重入性2、线程特有数据2.1 该特有数据从哪里来,该如何创建使用3、线程局部存储
一、简介线程是操作系统能够进行运算调度的最小单位。与进程相似,是允许应用程序并发执行多任务的机制,大部分情况下,它被包含在进程之中,是进程中的实际运作单位。
一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
1、线程与进程间对比线程是独立调度和分派的基本单位。线程可以为操作系统内核调度的内核线程`,如Win32线程;由用户进程自行调度的用户线程,如Linux平台的POSIX Thread;或者由内核与用户进程,如Windows 7的线程,进行混合调度;同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等;但同一进程中的多个线程有各自的调用栈,寄存器环境,线程本地存储;
通信便捷
1)线程能够便捷地共享信息,只需将data复制到共享变量中即可,而进程间并未有共享内存,故需要采取IPC进行通信;
创建速度
2)线程的创建速度相比于进程快很多,在Linux下,线程是通过clone()来实现线程,相比于进程的fork(),fork在创建的时机需要复制许多内存页表和文件描述符表等进程间的属性,而由于线程是共享机制,在创建是无需进行复制信息,故开销远大于fork();
稳定性
二、线程的相关属性及其使用方法 2、pthread_create3)当一个进程崩溃后,在保护模式下不会对其他进程产生影响,而当线程崩溃时会导致整个进程崩溃,由于所有线程共享进程的内存,故多进程比多线程更加稳定;
该函数是通过对glibc库的clone()进行封装使用,调用该函数即可实现将参数arg传入start_routine并执行;
案例
#include3、pthread_exit#include #include using namespace std; void *test_func(void *arg){ cout << (int *)arg << "th I am 【" << pthread_self() << "】thread" << endl; return nullptr; } int main(int argc, char* argv[]) { pthread_t tid; int ret, i; for(i=0; i < 5; ++i){ if((ret = pthread_create(&tid, NULL, test_func, (void *)i)) != 0){ cout << "pthread error" << endl; exit(1); } } pthread_exit(NULL); }
4、pthread_join
案例
#include5、pthread_detach#include #include using namespace std; typedef struct { std::string name; int age; }exit_t; void *func(void *){ exit_t *m_t = new exit_t; m_t->name = "xiaomi"; m_t->age = 15; cout << "I am thread..." << endl; pthread_exit((void *)m_t); } int main(int argc, char* argv[]) { pthread_t tid; exit_t *m_t; int ret; cout << "I am main..." << endl; if((ret = pthread_create(&tid, NULL, func, NULL)) != 0){ cout << "create error" << endl; exit(1); } pthread_join(tid, (void **)&m_t); cout << "name: 【" << m_t->name << "】" << " age:【" << m_t->age << "】" << endl; pthread_exit((void *)1); return 0; }
案例
#include6、pthread_cancel#include using namespace std; void *func(void *arg){ cout << "I am pthread" << endl; return (void *)1; } int main(int argc, char* argv[]) { pthread_t tid; pthread_create(&tid, NULL, func, NULL); cout << "I am main" << endl; int ret = pthread_detach(tid); cout << "detach pthread => " << ret << endl; return 0; }
案例
#include三、线程同步 1、简介#include #include using namespace std; void *func(void *arg){ while(1){ cout << "---------------" << endl; sleep(1); } pthread_testcancel(); } int main(int argc, char* argv[]) { pthread_t tid; pthread_create(&tid, NULL, func, NULL); cout << "I am main" << endl; sleep(3); pthread_cancel(tid); return 0; }
多线程通过特定的设置来控制线程之间的执⾏顺序,保证在多个线程下,共享资源不会被混淆,也可以说在线程之间通过同步建⽴起执⾏顺序的关系;
主要方式
2、互斥量互斥量;信号量;条件变量;
根据前面对线程的简介可知,线程的优势在于阔以通过全局/静态变量来共享信息,但是为了让一个线程在操作的同时确保其他线程不参与对该变量的修改时,需要通过某些方式对该变量提供一种保护机制 —— 互斥量能够保证对任意共享资源的原子操作;
原子操作
2.1 互斥量如何确保共享资源不被其他线程干扰呢指在一次发生中生效。一个原子操作不能在中途停止:它要么完整发生,要么不发生。没有副作用的原子操作是可见的,直到完成
使用锁机制,通过对该变量的加锁,解锁来达到对该变量的保护;
2.2 pthread_mutex_init【注意】:若应该对该资源进行锁定,在次加锁可能会导致线程阻塞陷入死锁或报错失败;
2.3 pthread_mutex_lock
2.4 pthread_mutex_unlock
2.5 pthread_mutex_destroy
案例
#include2.5 如何避免死锁#include #include using namespace std; pthread_mutex_t mutex; void *func(void *arg){ while(1){ pthread_mutex_lock(&mutex); cout << "I am pthread" << endl; sleep(1); pthread_mutex_unlock(&mutex); sleep(2); } return (void *)1; } int main(int argc, char* argv[]) { pthread_t tid; pthread_mutex_init(&mutex, NULL); pthread_create(&tid, NULL, func, NULL); while(1){ pthread_mutex_lock(&mutex); cout << "I am main" << endl; sleep(1); pthread_mutex_unlock(&mutex); sleep(2); } pthread_join(tid, NULL); pthread_mutex_destroy(&mutex); return 0; }
当超过一个线程加锁同一组互斥量时,就可能发生死锁;
3、条件变量避免死锁,最简单有效的方法就是定义互斥量的层级关系,当多个线程不能总是先锁定mutex1,在锁定mutex2,交叉互换即可;
条件变量允许一个线程就某个共享变量的状态变化通知其他线程(休眠),且让其他线程等待;
3.1 pthread_cond_init条件变量一般会结合互斥量使用,条件变量对共享变量的状态改变发出通知,而互斥量则提供对该共享变量访问的互斥;
3.2 pthread_cond_destroy
3.3 pthread_cond_wait
将线程阻塞,直到接收到条件变量的通知;
【步骤】: > - 解锁mutex;执行效率较高 > - 阻塞调用线程;若线程唤醒后,仍处于加锁状态则会立即再次休眠 > - 重新锁定mutex; 【注意】: > 当出现针对同一条件变量并发调用该函数,若使用多个互斥量,则会导致未定义;3.4 pthread_cond_signal
发生信号,当线程处于等待的状态时,某个变量的状态已经改变,即给该线程发送信号;
pthread_cond_broadcast
四、线程安全 1、函数的可重入性
2、线程特有数据该函数能够在多个线程无需使用互斥量等方式即可安全中调用,没有存在全局/静态变量的使用,将需要返回的信息存储与传入的分配buf中;
系统调用函数中有需要函数是可重入的,对于不可重入的函数,提供了定义后缀为_r的可重入函数;而常用的malloc函数的线程安全是通过互斥量来实现的;
2.1 该特有数据从哪里来,该如何创建使用确保函数在被线程调用时分别维护一份变量副本【初次分配依次即可】,且长期存在,即无需修改函数接口即可实现线程安全,但效率相比会降低;
当线程退出时,将会自动释放为该线程分配的存储;该副本不采用自动变量以及静态变量存放,由于前者离开该函数即自动释放,而后者在进程只维护一个实例;
此前会在首个调用该函数的线程中创建一个key,用来区别不同线程的数据项;
如何解决在每次进入该函数都能保证创建一次,而下次不在被重新创建呢?------->pthread_once()
pthread_key_create
【注意】:若一个线程有多个key,其析构函数应相互独立;
pthread_once
pthread_getspecific
pthread_setspecific
NPTL下实现方式
会该进程下在定义一个全局变量数组,存放key信息;每个线程都会包含一个数组,存放为每个线程分配的线程特有数据块的指针;
能够持久的为每个线程存储,相比于特有数据使用简便,只需要在全局/静态后增加__thread即可;
每个线程都能够拥有对该变量的拷贝,当线程终止时,会自动释放;
而在C++11中可以使用thread_local该关键字
能够在线程中创建一个全局变量或对象的本地副本,可以避免多线程情形下的资源竞争;
于线程存储期。对象的存储在线程开始时分配,而在线程结束时解分配。每个线程拥有其自身的对象实例。唯有声明为 thread_local 的对象拥有此存储期。能与 static、extern一同出现。
static thread_local int thread_count = 1;



