PHP 高级编程之多线程-消息队列

1. 多线程环境安装

1.1. PHP 5.5.9

安装PHP 5.5.9

https://github.com/oscm/shell/blob/master/php/5.5.9.sh
./configure --prefix=/srv/php-5.5.9 \--with-config-file-path=/srv/php-5.5.9/etc \--with-config-file-scan-dir=/srv/php-5.5.9/etc/conf.d \--enable-fpm \--with-fpm-user=www \--with-fpm-group=www \--with-pear \--with-curl \--with-gd \--with-jpeg-dir \--with-png-dir \--with-freetype-dir \--with-zlib-dir \--with-iconv \--with-mcrypt \--with-mhash \--with-pdo-mysql \--with-mysql-sock=/var/lib/mysql/mysql.sock \--with-openssl \--with-xsl \--with-recode \--enable-sockets \--enable-soap \--enable-mbstring \--enable-gd-native-ttf \--enable-zip \--enable-xml \--enable-bcmath \--enable-calendar \--enable-shmop \--enable-dba \--enable-wddx \--enable-sysvsem \--enable-sysvshm \--enable-sysvmsg \--enable-opcache \--enable-pcntl \--enable-maintainer-zts \--disable-debug

编译必须启用zts支持否则无法安装 pthreads(–enable-maintainer-zts)

1.2. 安装 pthreads 扩展

安装https://github.com/oscm/shell/blob/master/php/pecl/pthreads.sh

# curl -s https://raw.github.com/oscm/shell/master/php/pecl/pthreads.sh | bash

查看pthreads是否已经安装

# php -m | grep pthreads

2. Thread

<?phpclass HelloWorld extends Thread {    public function __construct($world) {       $this->world = $world;    }     public function run() {        print_r(sprintf("Hello %s\n", $this->world));    }} $thread = new HelloWorld("World"); if ($thread->start()) {    printf("Thread #%lu says: %s\n", $thread->getThreadId(), $thread->join());}?>

3. Worker 与 Stackable

<?phpclass SQLQuery extends Stackable {         public function __construct($sql) {                $this->sql = $sql;        }         public function run() {                $dbh  = $this->worker->getConnection();                $row = $dbh->query($this->sql);                while($member = $row->fetch(PDO::FETCH_ASSOC)){                        print_r($member);                }        } } class ExampleWorker extends Worker {        public static $dbh;        public function __construct($name) {        }         /*        * The run method should just prepare the environment for the work that is coming ...        */        public function run(){                self::$dbh = new PDO('mysql:host=192.168.2.1;dbname=example','www','123456');        }        public function getConnection(){                return self::$dbh;        }} $worker = new ExampleWorker("My Worker Thread"); $work=new SQLQuery('select * from members order by id desc limit 5');$worker->stack($work); $table1 = new SQLQuery('select * from demousers limit 2');$worker->stack($table1); $worker->start();$worker->shutdown();?>

4. 互斥锁

什么情况下会用到互斥锁?在你需要控制多个线程同一时刻只能有一个线程工作的情况下可以使用。

下面我们举一个例子,一个简单的计数器程序,说明有无互斥锁情况下的不同。

<?php$counter = 0;//$handle=fopen("php://memory", "rw");//$handle=fopen("php://temp", "rw");$handle=fopen("/tmp/counter.txt", "w");fwrite($handle, $counter );fclose($handle); class CounterThread extends Thread {public function __construct($mutex = null){ose($this->handle);}    public function run() {if($this->mutex)$locked=Mutex::lock($this->mutex); $counter = intval(fgets($this->handle));$counter++;rewind($this->handle);fputs($this->handle, $counter );printf("Thread #%lu says: %s\n", $this->getThreadId(),$counter); if($this->mutex)Mutex::unlock($this->mutex);    }} //没有互斥锁for ($i=0;$i<50;$i++){$threads[$i] = new CounterThread();$threads[$i]->start(); } //加入互斥锁$mutex = Mutex::create(true);for ($i=0;$i<50;$i++){$threads[$i] = new CounterThread($mutex);$threads[$i]->start(); } Mutex::unlock($mutex);for ($i=0;$i<50;$i++){$threads[$i]->join();}Mutex::destroy($mutex); ?>

我们使用文件/tmp/counter.txt保存计数器值,每次打开该文件将数值加一,然后写回文件。当多个线程同时操作一个文件的时候,就会线程运行先后取到的数值不同,写回的数值也不同,最终计数器的数值会混乱。

没有加入锁的结果是计数始终被覆盖,最终结果是2

而加入互斥锁后,只有其中的一个进程完成加一工作并释放锁,其他线程才能得到解锁信号,最终顺利完成计数器累加操作

上面例子也可以通过对文件加锁实现,这里主要讲的是多线程锁,后面会涉及文件锁。

4.1. 多线程与共享内存

在共享内存的例子中,没有使用任何锁,仍然可能正常工作,可能工作内存操作本身具备锁的功能。

<?php$tmp = tempnam(__FILE__, 'PHP');$key = ftok($tmp, 'a'); $shmid = shm_attach($key);$counter = 0;shm_put_var( $shmid, 1, $counter ); class CounterThread extends Thread {public function __construct($shmid){        $this->shmid = $shmid;    }    public function run() { $counter = shm_get_var( $this->shmid, 1 );$counter++;shm_put_var( $this->shmid, 1, $counter ); printf("Thread #%lu says: %s\n", $this->getThreadId(),$counter);    }} for ($i=0;$i<100;$i++){$threads[] = new CounterThread($shmid);}for ($i=0;$i<100;$i++){$threads[$i]->start(); } for ($i=0;$i<100;$i++){$threads[$i]->join();}shm_remove( $shmid );shm_detach( $shmid );?>

5. 线程同步

有些场景我们不希望 thread->start() 就开始运行程序,而是希望线程等待我们的命令。

$thread->wait();测作用是 thread->start()后线程并不会立即运行,只有收到 $thread->notify(); 发出的信号后才运行

<?php$tmp = tempnam(__FILE__, 'PHP');$key = ftok($tmp, 'a'); $shmid = shm_attach($key);$counter = 0;shm_put_var( $shmid, 1, $counter ); class CounterThread extends Thread {public function __construct($shmid){        $this->shmid = $shmid;    }    public function run() {         $this->synchronized(function($thread){            $thread->wait();        }, $this); $counter = shm_get_var( $this->shmid, 1 );$counter++;shm_put_var( $this->shmid, 1, $counter ); printf("Thread #%lu says: %s\n", $this->getThreadId(),$counter);    }} for ($i=0;$i<100;$i++){$threads[] = new CounterThread($shmid);}for ($i=0;$i<100;$i++){$threads[$i]->start(); } for ($i=0;$i<100;$i++){$threads[$i]->synchronized(function($thread){$thread->notify();}, $threads[$i]);} for ($i=0;$i<100;$i++){$threads[$i]->join();}shm_remove( $shmid );shm_detach( $shmid );?>

6. 线程池

6.1. 线程池

自行实现一个Pool类

<?phpclass Update extends Thread {     public $running = false;    public $row = array();    public function __construct($row) { $this->row = $row;        $this->sql = null;    }     public function run() { if(strlen($this->row['bankno']) > 100 ){$bankno = safenet_decrypt($this->row['bankno']);}else{$error = sprintf("%s, %s\r\n",$this->row['id'], $this->row['bankno']);file_put_contents("bankno_error.log", $error, FILE_APPEND);} if( strlen($bankno) > 7 ){$sql = sprintf("update members set bankno = '%s' where id = '%s';", $bankno, $this->row['id']); $this->sql = $sql;} printf("%s\n",$this->sql);    } } class Pool {public $pool = array();public function __construct($count) {$this->count = $count;}public function push($row){if(count($this->pool) < $this->count){$this->pool[] = new Update($row);return true;}else{return false;}}public function start(){foreach ( $this->pool as $id => $worker){$this->pool[$id]->start();}}public function join(){foreach ( $this->pool as $id => $worker){               $this->pool[$id]->join();}}public function clean(){foreach ( $this->pool as $id => $worker){if(! $worker->isRunning()){            unset($this->pool[$id]);            }}}} try {$dbh    = new PDO("mysql:host=" . str_replace(':', ';port=', $dbhost) . ";dbname=$dbname", $dbuser, $dbpw, array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'',PDO::MYSQL_ATTR_COMPRESS => true)); $sql  = "select id,bankno from members order by id desc";$row = $dbh->query($sql);$pool = new Pool(5);while($member = $row->fetch(PDO::FETCH_ASSOC)){ while(true){if($pool->push($member)){ //压入任务到池中break;}else{ //如果池已经满,就开始启动线程$pool->start();$pool->join();$pool->clean();}}}$pool->start();    $pool->join(); $dbh = null; } catch (Exception $e) {    echo '[' , date('H:i:s') , ']', '系统错误', $e->getMessage(), "\n";}?>

6.2. 动态队列线程池

上面的例子是当线程池满后执行start统一启动,下面的例子是只要线程池中有空闲便立即创建新线程。

<?phpclass Update extends Thread {     public $running = false;    public $row = array();    public function __construct($row) { $this->row = $row;        $this->sql = null;//print_r($this->row);    }     public function run() { if(strlen($this->row['bankno']) > 100 ){$bankno = safenet_decrypt($this->row['bankno']);}else{$error = sprintf("%s, %s\r\n",$this->row['id'], $this->row['bankno']);file_put_contents("bankno_error.log", $error, FILE_APPEND);} if( strlen($bankno) > 7 ){$sql = sprintf("update members set bankno = '%s' where id = '%s';", $bankno, $this->row['id']); $this->sql = $sql;} printf("%s\n",$this->sql);    } }   try {$dbh    = new PDO("mysql:host=" . str_replace(':', ';port=', $dbhost) . ";dbname=$dbname", $dbuser, $dbpw, array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'',PDO::MYSQL_ATTR_COMPRESS => true)); $sql     = "select id,bankno from members order by id desc limit 50"; $row = $dbh->query($sql);$pool = array();while($member = $row->fetch(PDO::FETCH_ASSOC)){$id = $member['id'];while (true){if(count($pool) < 5){$pool[$id] = new Update($member);$pool[$id]->start();break;}else{foreach ( $pool as $name => $worker){if(! $worker->isRunning()){unset($pool[$name]);}}}} } $dbh = null; } catch (Exception $e) {    echo '【' , date('H:i:s') , '】', '【系统错误】', $e->getMessage(), "\n";}?>

6.3. pthreads Pool类

pthreads 提供的 Pool class 例子

<?php class WebWorker extends Worker { public function __construct(SafeLog $logger) {$this->logger = $logger;} protected $loger;} class WebWork extends Stackable { public function isComplete() {return $this->complete;} public function run() {$this->worker->logger->log("%s executing in Thread #%lu",  __CLASS__, $this->worker->getThreadId());$this->complete = true;} protected $complete;} class SafeLog extends Stackable { protected function log($message, $args = []) {$args = func_get_args(); if (($message = array_shift($args))) {echo vsprintf("{$message}\n", $args);}}}  $pool = new Pool(8, \WebWorker::class, [new SafeLog()]); $pool->submit($w=new WebWork());$pool->submit(new WebWork());$pool->submit(new WebWork());$pool->submit(new WebWork());$pool->submit(new WebWork());$pool->submit(new WebWork());$pool->submit(new WebWork());$pool->submit(new WebWork());$pool->submit(new WebWork());$pool->submit(new WebWork());$pool->submit(new WebWork());$pool->submit(new WebWork());$pool->submit(new WebWork());$pool->submit(new WebWork());$pool->shutdown(); $pool->collect(function($work){return $work->isComplete();}); var_dump($pool);

7. 多线程文件安全读写(文件锁)

文件所种类。

LOCK_SH 取得共享锁定(读取的程序)。LOCK_EX 取得独占锁定(写入的程序。LOCK_UN 释放锁定(无论共享或独占)。LOCK_NB 如果不希望 flock() 在锁定时堵塞

共享锁例子

<?php $fp = fopen("/tmp/lock.txt", "r+"); if (flock($fp, LOCK_EX)) {  // 进行排它型锁定    ftruncate($fp, 0);      // truncate file    fwrite($fp, "Write something here\n");    fflush($fp);            // flush output before releasing the lock    flock($fp, LOCK_UN);    // 释放锁定} else {    echo "Couldn't get the lock!";} fclose($fp); ?>

共享锁例子2

<?php$fp = fopen('/tmp/lock.txt', 'r+'); /* Activate the LOCK_NB option on an LOCK_EX operation */if(!flock($fp, LOCK_EX | LOCK_NB)) {    echo 'Unable to obtain lock';    exit(-1);} /* ... */ fclose($fp);?>

8. 多线程与数据连接

pthreads 与 pdo 同时使用是,需要注意一点,需要静态声明public static $dbh;并且通过单例模式访问数据库连接。

8.1. Worker 与 PDO

<?phpclass Work extends Stackable {         public function __construct() {        }         public function run() {                $dbh  = $this->worker->getConnection();                $sql     = "select id,name from members order by id desc limit 50";                $row = $dbh->query($sql);                while($member = $row->fetch(PDO::FETCH_ASSOC)){                        print_r($member);                }        } } class ExampleWorker extends Worker {        public static $dbh;        public function __construct($name) {        }         /*        * The run method should just prepare the environment for the work that is coming ...        */        public function run(){                self::$dbh = new PDO('mysql:host=192.168.2.1;dbname=example','www','123456');        }        public function getConnection(){                return self::$dbh;        }} $worker = new ExampleWorker("My Worker Thread"); $work=new Work();$worker->stack($work); $worker->start();$worker->shutdown();?>

8.2. Pool 与 PDO

在线程池中链接数据库

# cat pool.php<?phpclass ExampleWorker extends Worker { public function __construct(Logging $logger) {$this->logger = $logger;} protected $logger;} /* the collectable class implements machinery for Pool::collect */class Work extends Stackable {public function __construct($number) {$this->number = $number;}public function run() {                $dbhost = 'db.example.com';               // 数据库服务器                $dbuser = 'example.com';                 // 数据库用户名                $dbpw = 'password';                               // 数据库密码                $dbname = 'example_real';$dbh  = new PDO("mysql:host=$dbhost;port=3306;dbname=$dbname", $dbuser, $dbpw, array(                        PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'',                        PDO::MYSQL_ATTR_COMPRESS => true,PDO::ATTR_PERSISTENT => true                        )                );$sql = "select OPEN_TIME, `COMMENT` from MT4_TRADES where LOGIN='".$this->number['name']."' and CMD='6' and `COMMENT` = '".$this->number['order'].":DEPOSIT'";#echo $sql;$row = $dbh->query($sql);$mt4_trades  = $row->fetch(PDO::FETCH_ASSOC);if($mt4_trades){ $row = null; $sql = "UPDATE db_example.accounts SET paystatus='成功', deposit_time='".$mt4_trades['OPEN_TIME']."' where `order` = '".$this->number['order']."';";$dbh->query($sql);.example.com';                      // 数据库服务器$dbuser = 'example.com';                 // 数据库用户名$dbpw = 'password';                               // 数据库密码$dbname = 'db_example';$dbh    = new PDO("mysql:host=$dbhost;port=3306;dbname=$dbname", $dbuser, $dbpw, array(                        PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'',                        PDO::MYSQL_ATTR_COMPRESS => true                        )                );$sql = "select `order`,name from accounts where deposit_time is null order by id desc"; $row = $dbh->query($sql);while($account = $row->fetch(PDO::FETCH_ASSOC)){        $pool->submit(new Work($account));} $pool->shutdown(); ?>

进一步改进上面程序,我们使用单例模式 $this->worker->getInstance(); 全局仅仅做一次数据库连接,线程使用共享的数据库连接

<?phpclass ExampleWorker extends Worker { #public function __construct(Logging $logger) {#$this->logger = $logger;#} #protected $logger;protected  static $dbh;public function __construct() { }public function run(){$dbhost = 'db.example.com';// 数据库服务器    $dbuser = 'example.com';        // 数据库用户名        $dbpw = 'password';             // 数据库密码$dbname = 'example';// 数据库名 self::$dbh  = new PDO("mysql:host=$dbhost;port=3306;dbname=$dbname", $dbuser, $dbpw, array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'',PDO::MYSQL_ATTR_COMPRESS => true,PDO::ATTR_PERSISTENT => true)); }protected function getInstance(){        return self::$dbh;    } } /* the collectable class implements machinery for Pool::collect */class Work extends Stackable {public function __construct($data) {$this->data = $data;#print_r($data);} public function run() {#$this->worker->logger->log("%s executing in Thread #%lu", __CLASS__, $this->worker->getThreadId() ); try {$dbh  = $this->worker->getInstance();#print_r($dbh);               $id = $this->data['id'];$mobile = safenet_decrypt($this->data['mobile']);#printf("%d, %s \n", $id, $mobile);if(strlen($mobile) > 11){$mobile = substr($mobile, -11);}if($mobile == 'null'){#$sql = "UPDATE members_digest SET mobile = '".$mobile."' where id = '".$id."'";#printf("%s\n",$sql);#$dbh->query($sql);$mobile = '';$sql = "UPDATE members_digest SET mobile = :mobile where id = :id";}else{$sql = "UPDATE members_digest SET mobile = md5(:mobile) where id = :id";}$sth = $dbh->prepare($sql);$sth->bindValue(':mobile', $mobile);$sth->bindValue(':id', $id);$sth->execute();#echo $sth->debugDumpParams();}catch(PDOException $e) {$error = sprintf("%s,%s\n", $mobile, $id );file_put_contents("mobile_error.log", $error, FILE_APPEND);} #$dbh = null;printf("runtime: %s, %s, %s, %s\n", date('Y-m-d H:i:s'), $this->worker->getThreadId() ,$mobile, $id);#printf("runtime: %s, %s\n", date('Y-m-d H:i:s'), $this->number);}} $pool = new Pool(100, \ExampleWorker::class, []); #foreach (range(0, 100) as $number) {#$pool->submit(new Work($number));#} $dbhost = 'db.example.com';                     // 数据库服务器$dbuser = 'example.com';                 // 数据库用户名$dbpw = 'password';                             // 数据库密码$dbname = 'example';$dbh    = new PDO("mysql:host=$dbhost;port=3307;dbname=$dbname", $dbuser, $dbpw, array(                        PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'',                        PDO::MYSQL_ATTR_COMPRESS => true                        )                );#print_r($dbh); #$sql = "select id, mobile from members where id < :id";#$sth = $dbh->prepare($sql);#$sth->bindValue(':id',300);#$sth->execute();#$result = $sth->fetchAll();#print_r($result);##$sql = "UPDATE members_digest SET mobile = :mobile where id = :id";#$sth = $dbh->prepare($sql);#$sth->bindValue(':mobile', 'aa');#$sth->bindValue(':id','272');#echo $sth->execute();#echo $sth->queryString;#echo $sth->debugDumpParams();  $sql = "select id, mobile from members order by id asc"; // limit 1000";$row = $dbh->query($sql);while($members = $row->fetch(PDO::FETCH_ASSOC)){        #$order =  $account['order'];        #printf("%s\n",$order);        //print_r($members);        $pool->submit(new Work($members));#unset($account['order']);} $pool->shutdown(); ?>

8.3. 多线程中操作数据库总结

总的来说 pthreads 仍然处在发展中,仍有一些不足的地方,我们也可以看到pthreads的git在不断改进这个项目

数据库持久链接很重要,否则每个线程都会开启一次数据库连接,然后关闭,会导致很多链接超时。

<?php$dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass, array(    PDO::ATTR_PERSISTENT => true));?>

9. Thread And ZeroMQ

应用场景,我使用触发器监控数据库某个表,一旦发现有改变就通知程序处理数据

9.1. 数据库端

首先安装ZeroMQ 与 MySQL UDF https://github.com/netkiller/mysql-zmq-plugin, 然后创建触发器。

CREATE DEFINER=`dba`@`192.168.%` PROCEDURE `Table_Example`(IN `TICKET` INT, IN `LOGIN` INT, IN `CMD` INT, IN `VOLUME` INT)LANGUAGE SQLNOT DETERMINISTIC READS SQL DATASQL SECURITY DEFINER COMMENT '交易监控'BEGIN DECLARE Example CHAR(1) DEFAULT 'N'; IF CMD IN ('0','1') THEN IF VOLUME >=10 AND VOLUME <=90 THEN select coding into Example from example.members where username = LOGIN and coding = 'Y';IF Example = 'Y' THEN select zmq_client('tcp://192.168.2.15:5555', CONCAT(TICKET, ',', LOGIN, ',', VOLUME));END IF;END IF;END IF;END CREATE DEFINER=`dba`@`192.168.6.20` TRIGGER `Table_AFTER_INSERT` AFTER INSERT ON `MT4_TRADES` FOR EACH ROW BEGIN call Table_Example(NEW.TICKET,NEW.LOGIN,NEW.CMD,NEW.VOLUME);END

9.2. 数据处理端

<?phpclass ExampleWorker extends Worker { #public function __construct(Logging $logger) {#$this->logger = $logger;#} #protected $logger;protected  static $dbh;public function __construct() { }public function run(){$dbhost = '192.168.2.1';// 数据库服务器$dbport = 3306;    $dbuser = 'www';        // 数据库用户名        $dbpass = 'password';             // 数据库密码$dbname = 'example';// 数据库名 self::$dbh  = new PDO("mysql:host=$dbhost;port=$dbport;dbname=$dbname", $dbuser, $dbpass, array(/* PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'', */PDO::MYSQL_ATTR_COMPRESS => true,PDO::ATTR_PERSISTENT => true)); }protected function getInstance(){        return self::$dbh;    } } /* the collectable class implements machinery for Pool::collect */class Fee extends Stackable {public function __construct($msg) {$trades = explode(",", $msg);$this->data = $trades;print_r($trades);} public function run() {#$this->worker->logger->log("%s executing in Thread #%lu", __CLASS__, $this->worker->getThreadId() ); try {$dbh  = $this->worker->getInstance(); $insert = "INSERT INTO coding_fee(ticket, login, volume, `status`) VALUES(:ticket, :login, :volume,'N')";$sth = $dbh->prepare($insert);$sth->bindValue(':ticket', $this->data[0]);$sth->bindValue(':login', $this->data[1]);$sth->bindValue(':volume', $this->data[2]);$sth->execute();//$sth = null;//$dbh = null; /* 业务实现在此处 */ $update = "UPDATE coding_fee SET `status` = 'Y' WHERE ticket = :ticket and `status` = 'N'";$sth = $dbh->prepare($update);$sth->bindValue(':ticket', $this->data[0]);$sth->execute();//echo $sth->queryString;}catch(PDOException $e) {$error = sprintf("%s,%s\n", $mobile, $id );file_put_contents("mobile_error.log", $error, FILE_APPEND);} #$dbh = null;//printf("runtime: %s, %s, %s, %s\n", date('Y-m-d H:i:s'), $this->worker->getThreadId() ,$mobile, $id);#printf("runtime: %s, %s\n", date('Y-m-d H:i:s'), $this->number);}} class Example {/* config */const LISTEN = "tcp://192.168.2.15:5555";const MAXCONN = 100;const pidfile = __CLASS__;const uid= 80;const gid= 80; protected $pool = NULL;protected $zmq = NULL;public function __construct() {$this->pidfile = '/var/run/'.self::pidfile.'.pid';}private function daemon(){if (file_exists($this->pidfile)) {echo "The file $this->pidfile exists.\n";exit();} $pid = pcntl_fork();if ($pid == -1) { die('could not fork');} else if ($pid) { // we are the parent //pcntl_wait($status); //Protect against Zombie childrenexit($pid);} else {// we are the childfile_put_contents($this->pidfile, getmypid());posix_setuid(self::uid);posix_setgid(self::gid);return(getmypid());}}private function start(){$pid = $this->daemon();$this->pool = new Pool(self::MAXCONN, \ExampleWorker::class, []);$this->zmq = new ZMQSocket(new ZMQContext(), ZMQ::SOCKET_REP);$this->zmq->bind(self::LISTEN); /* Loop receiving and echoing back */while ($message = $this->zmq->recv()) {if($message){$this->pool->submit(new Fee($message));$this->zmq->send('TRUE');}else{$this->zmq->send('FALSE');}}$pool->shutdown();}private function stop(){ if (file_exists($this->pidfile)) {$pid = file_get_contents($this->pidfile);posix_kill($pid, 9);unlink($this->pidfile);}}private function help($proc){printf("%s start | stop | help \n", $proc);}public function main($argv){if(count($argv) < 2){printf("please input help parameter\n");exit();}if($argv[1] === 'stop'){$this->stop();}else if($argv[1] === 'start'){$this->start();}else{$this->help($argv[0]);}}} $example = new Example();$example->main($argv);

使用方法

# php example.php start# php example.php stop# php example.php help

此程序涉及守候进程实现$this->daemon()运行后转到后台运行,进程ID保存,进程的互斥(不允许同时启动两个进程),线程池连接数以及线程任务等等

转载自http://my.oschina.net/neochen/blog/294354

用Python做一只真·多足机器人,钢铁蜈蚣能弯曲还能蠕动

大数据文摘出品

来源:declanoller

编译:徐玲、李世林、陈若朦

足式机器人是如今机器人设计的热点,相较于轮式和履带式机器人,足式设计的优势在于其极强的地形通过能力。

你一定见过模仿人类的两足机器人、犬型和马型的四足机器人、近来爆红的蜘蛛型六足机器人,那你有想过再多来几条腿吗?

控制行走一直是足式机器人的一大设计难点,腿越多则移动越困难。然而,一位名叫Adimin的外国小哥用python做了一只可爬行可弯曲的蜈蚣型机器人。

问:为什么要做设计成蜈蚣型呢?

小哥答:蜈蚣在体型上具有相当的长度,而通过向上弯曲身体还可以具有一定的高度。但是重点是——从来没人做过!蜈蚣机器人够酷、够怪、够有趣!

让“蜈蚣”蠕动起来:双伺服旋转的腿

为实现“蜈蚣”的移动,同时考虑到电力需求,Adimin设计了腿在水平和垂直方向上的旋转功能,分别命名为hip servo 和ankle servo。

于是,腿就能在两个方向上蠕动起来!

此处的所有伺服均由PCA9685 PWM分支板控制——这是一个I2C器件,允许同时控制多达16个伺服器,既便宜又实用。

考虑腿的数量和“蜈蚣”身体的的连接方式,Adimin小哥将主体平台部分设计得较大,给腿的添加留出更多空间;同时在前后两端采取铰链设计(采用金属齿轮MG 996R),不仅能实现身体长度的延伸,还能完成向上弯曲的动作。

“蜈蚣”弯曲起来!

用Python制作多足机器人

“蜈蚣”运动的控制代码是一个分层的类结构。

最基本的单元是Servo部分,使用这部分功能可以直接控制伺服器。

代码中更高级的部分是Leg,其中包含了两个Servo对象,分别用来控制之前提到的 “hip” 和 “ankle” 伺服系统,根据其自身leg_index(2*leg_index和2*leg_index + 1)为它们分配正确的板索引值。

LegPair部分与之Leg类似类似,其中创建了两个Leg对象分别控制左右。

1. b = DriverBoard(args.addr, 16)2.3. ifargs.N_pairs > 4:4. b_front = DriverBoard(40, 8)5.6. pairs = []7. for i in range(args.N_pairs):8. if i < 4:9. lp = LegPair(b, i)10. else:11. lp = LegPair(b_front, i-4)12.13. pairs.append(lp)14.15. start = time()16. while True:17. forp in pairs:18. p.increment_ankles()

让我们来看看这段代码的功能——“蜈蚣”弹跳起来了!

其实,让“蜈蚣”实现行走的部分是increment _ankles()函数。为了解释这一点,让我们回到Servo class。

伺服系统的主要工作是循环移动。为了控制伺服装置的位置,需要向它发送一个特定占空比的脉宽调制(PWM)信号。接下来,我们要找到对应于该点的脉宽调制,即中点脉宽调制(mid_pwm),使它围绕一个点振荡。然后,定义一个脉宽调制幅度(pwm_amplitude),该幅度会决定它在这个循环中相对于中点上下移动的距离。于是,让“蜈蚣”循环移动起来只需通过以下代码:

pwm = int(self.pwm_mid + pwm_amplitude*sin(self.phase + self.phase_offset))

如果要让一条腿以我们预期的“行走”方式运动,hip和ankle伺服系统不可能做完全相同的运动。让我们将运动参数化成x和y,加上时间变量t,构成一个正弦函数,令x(t)=y(t)=sin(ωt),便可以得到下面这个运动曲线:

为了实现行走,则还需要您给其中一个变量提供相位偏移(如上面的代码所示),最终得到一个圆。

在伺服系统的相位偏移变量设计中,Adimin表示分层设置实在是太炫酷了——它能使多条腿连贯运动!

每个腿的伺服对象之间需要一定的相位偏移,而每条腿相对于其他腿也存在相位偏移。因此,我们需要给每个LegPair对象一个高级的相位偏移量,然后每个部分将相应的偏移量分配给它的低级对象。

除了上下跳动以外,Python还能实现“蜈蚣”的其他运动方式:

1. b = DriverBoard(args.addr, 16)2.3. ifargs.N_pairs > 4:4. b_front = DriverBoard(40, 8)5.6. sleep(0.5)7. pairs = []8. for i in range(args.N_pairs):9. if i < 4:10. lp = LegPair(b, i)11. else:12. lp = LegPair(b_front, i-4)13.14. if i%2 == 1:15. lp.set_phase_offset(pi)16. pairs.append(lp)17.18. start = time()19. time_limit = args.runtime20.21. while True:22. forp in pairs:23. p.increment()

自然生物学为机器人设计提供了黄金标准,我们需仍要很长时间才能制造出完成一切动物行为动作的多足机器人。而另一方面,机器人不受生物学的限制,这意味着它们总有可能学习动物天生就不会的新行为。

php判断当前是 http还是 https

** * 判断是否SSL协议
 * @return boolean */
function is_ssl() 
{ 
if(isset($_SERVER['HTTPS']) && ('1' == $_SERVER['HTTPS'] || 'on' == strtolower($_SERVER['HTTPS']))){
 return true; 
}elseif(isset($_SERVER['SERVER_PORT']) && ('443' == $_SERVER['SERVER_PORT'] )) { return true; 
} 
return false; 
}

使用

$http = 'http://'; $http =is_ssl()?'https://':$http;

Android Studio 提示Error running app: No Android facet found for app

错误解决办法如下:

可以通过以下几个步骤解决该问题: 

1) 点击菜单File -> 选择Project Structure, 或使用快捷键 (Ctrl+Alt+Shift+S) 打开”Project Structure”.

2) 然后选择”Project Settings” 下的”Facets” 栏

3) 在点击 “+” 号在第二栏的上面用来添加新的facets.

4) 通过”Add “菜单选择”Android” facet从那里会打开另一个对话框来 选择一个module. (选择一个你想要应用的facet)。

5)选择好之后就完成了!明星效应。很简单,在一个领域保持顶尖水平,比在一两个领域保持领先水平和五六个领域保持一般水准都要更有价值、并且收益更好。 有悖常识的真相:让未来更开放的方式,正是专注的去做好一件事情。这个世界上最成功的人,他们在某一领域获得成功之后,可通过经营杠杆进入任何他们想要涉足的领域。而这都得依赖于他们曾极致的专注在做好一件事情上。

报错:The Apache Tomcat installation at this directory is version 8.5.27. A Tomcat 8.0 installation is

今天在eclipse中配置tomcat时,遇到了一个报错,如下所示:

  这里我的Tomcat的版本是8.5.27,报这个错的原因是ellipse里面限制Tomcat的最高版本是8.0的,我用的tomcat的版本明显高于eclipse的要求;具体的改法如下:

  1.首先找到Tomcat的本地安装路径;

  2.然后找到lib文件夹中的Catalina.jar包,用解压软件打开这个jar包;

  3.依次找到并且双击打开catalina.jar\org\apache\catalina\util\ServerInfo.properties文件,如下所示:

  4.将文件中server.info=Apache Tomcat/8.5.27中的8.5.27改成8.0.0即可;

修改完成后重新配置Tomcat不在报错

  修改完成!

thinkphp5 join使用注意

A表有id,name,time等字段,
B表有id,type,uid,email,address等字段。
A表中的id和B表中的uid对应。

    Db::table(A表)->alias('a')
                ->join('B表 b', 'a.id = b.uid')
                ->find();

这样是把B表中的所有字段都给返回了,B表的字段会覆盖A中的同名字段,
比如最终返回的结果中id是B表中的id

这时要注意指定字段->field(‘a.*,b.email,b.adderss’)

PHP使用ffmpeg实现视频截图(Linux系统和windows系统)

环境:php5.6,apache2.2

windows7:

ffmpeg-windows下载地址:https://download.csdn.net/download/qq_39545346/10312836

说明:

将ffmpeg中的所有dll文件和ext文件扔到C盘下的system32文件夹。

执行下面的代码:
$name = md5(date(‘YmdHis’)).”.png”;  
        $from = “E:\1.mp4”;  
        $to = “E:\cover_images\”;  
        $str = “ffmpeg -i “.$from.” -y -f mjpeg -ss 3 -t 1 -s 740×500 “.$to.$name;  
        system($str);

Linux(centos6.8):

根据安装教程在linux上安装完ffmpeg
直接使用exec函数,在php代码中执行linux命令,即可进行截图:
exec(‘/usr/local/bin/ffmpeg -ss 00:00:01  -i ./1.mp4 ./pic/423.jpg  -r 1 -vframes 1 -an -f mjpeg 1>/dev/null’);
注意,运行时应保证以下几点:
1.保存截图文件的文件夹有相关权限,
2.截取的视频文件有相关权限,
3.php没有禁用exec()函数,在php.ini中可以查看disabled_function
4.web访问用户,即apache服务默认用户有执行ffmepg的权限,
apache默认用户在httpd.conf中查看。
5.修改etc下的sudoers文件,新增加
Defaults visiblepw
%apache ALL=(ALL) NOPASSWD:/usr/bin/sudo, /usr/local/bin/MP4Box, /usr/local/bin/ffmpeg
给予apache用户相关权限。

centos7下javac:未找到命令的问题

在linux下编译java程序,执行javac编译生成class文件时,在centos7终端输入如,javac hello.java    会提示未找到指令,但用java -verison测试环境变量是没问题的

百度了好久,说的很复杂,重新再linux配置环境变量,输入 vi /etc/profile进入,添加以下代码:

export JAVA_HOME=/usr/local/jdk1.8.0_144
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar

再测试,最后也没有成功

后来在stackoverflow上看到了这个

84 down vote accepted
You installed the Java Runtime Environment (JRE) only, which does not contain javac. For javac, you have to install the OpenJDK Development Environment. You can install java-devel or java-1.6.0-openjdk-devel, which both include javac.

By the way: you can find out which package provides javac with a yum search, e.g.

su -c ‘yum provides javac’
Another note: using yum and openjdk is only one possibility to install the JDK. Many people prefer Sun/Oracle’s “original” SDK. See How to install Java SDK on CentOS? and links for alternatives.

大意就是我们用yum来装原生的就行了

在终端输入

yum install java-devel

执行安装

再测试就行了


补充:Vi编辑常用快捷键

复制:ctrl+insert

粘贴:shift+insert

按Esc保存退出编译,shift+zz退出